From afb3a717f2aca9fc7ef22d8ef5630b7391d1d8b7 Mon Sep 17 00:00:00 2001 From: pelya Date: Mon, 27 Dec 2010 14:16:43 +0000 Subject: [PATCH 001/847] Added an option for selecting part of the text on the screen with two screen touches. --- AndroidManifest.xml | 2 +- res/values/strings.xml | 2 + src/jackpal/androidterm/Term.java | 170 +++++++++++++++++++++++++++--- 3 files changed, 161 insertions(+), 13 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 5f3fadbbb..a0551beff 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,6 +1,6 @@ + android:versionName="1.0.19" android:versionCode="20"> Toggle soft keyboard Edit text + Select text + Copy to clipboard Copy all Paste diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index 42eb81099..b6e2e2582 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -171,8 +171,9 @@ public class Term extends Activity { private SharedPreferences mPrefs; - private final static int COPY_ALL_ID = 0; - private final static int PASTE_ID = 1; + private final static int SELECT_TEXT_ID = 0; + private final static int COPY_ALL_ID = 1; + private final static int PASTE_ID = 2; private boolean mAlreadyStarted = false; @@ -462,6 +463,7 @@ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo); menu.setHeaderTitle(R.string.edit_text); + menu.add(0, SELECT_TEXT_ID, 0, !mEmulatorView.getSelectingText() ? R.string.select_text : R.string.select_text_done); menu.add(0, COPY_ALL_ID, 0, R.string.copy_all); menu.add(0, PASTE_ID, 0, R.string.paste); if (!canPaste()) { @@ -472,6 +474,11 @@ public void onCreateContextMenu(ContextMenu menu, View v, @Override public boolean onContextItemSelected(MenuItem item) { switch (item.getItemId()) { + case SELECT_TEXT_ID: + if(mEmulatorView.getSelectingText()) + doCopySelectedText(); + mEmulatorView.toggleSelectingText(); + return true; case COPY_ALL_ID: doCopyAll(); return true; @@ -523,7 +530,16 @@ private void doCopyAll() { clip.setText(mEmulatorView.getTranscriptText().trim()); } + private void doCopySelectedText() { + ClipboardManager clip = (ClipboardManager) + getSystemService(Context.CLIPBOARD_SERVICE); + clip.setText(mEmulatorView.getSelectedText().trim()); + } + private void doPaste() { + if(mEmulatorView.getSelectingText()) + doCopySelectedText(); + ClipboardManager clip = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); CharSequence paste = clip.getText(); @@ -636,6 +652,16 @@ void blockSet(int sx, int sy, int w, int h, int val, int foreColor, int */ String getTranscriptText(); + /** + * Get the selected text inside transcript buffer as a text string. + * @param x1 Selection start + * @param y1 Selection start + * @param x2 Selection end + * @param y2 Selection end + * @return the contents of the transcript buffer. + */ + String getSelectedText(int x1, int y1, int x2, int y2); + /** * Resize the screen * @param columns @@ -958,9 +984,11 @@ public void blockSet(int sx, int sy, int w, int h, int val, * @param y The y coordinate origin of the drawing * @param renderer The renderer to use to draw the text * @param cx the cursor X coordinate, -1 means don't draw it + * @param selx1 the text selection start X coordinate + * @param selx2 the text selection end X coordinate, if equals to selx1 don't draw selection */ public final void drawText(int row, Canvas canvas, float x, float y, - TextRenderer renderer, int cx) { + TextRenderer renderer, int cx, int selx1, int selx2) { // Out-of-bounds rows are blank. if (row < -mActiveTranscriptRows || row >= mScreenRows) { @@ -980,7 +1008,7 @@ public final void drawText(int row, Canvas canvas, float x, float y, for (int i = 0; i < columns; i++) { char c = data[offset + i]; int colors = (char) (c & 0xff00); - if (cx == i) { + if (cx == i || (i >= selx1 && i <= selx2)) { // Set cursor background color: colors |= CURSOR_MASK; } @@ -1023,10 +1051,14 @@ public int getActiveTranscriptRows() { } public String getTranscriptText() { - return internalGetTranscriptText(true); + return internalGetTranscriptText(true, 0, -mActiveTranscriptRows, mColumns, mScreenRows); } - private String internalGetTranscriptText(boolean stripColors) { + public String getSelectedText(int selX1, int selY1, int selX2, int selY2) { + return internalGetTranscriptText(true, selX1, selY1, selX2, selY2); + } + + private String internalGetTranscriptText(boolean stripColors, int selX1, int selY1, int selX2, int selY2) { StringBuilder builder = new StringBuilder(); char[] rowBuffer = mRowBuffer; char[] data = mData; @@ -1044,11 +1076,21 @@ private String internalGetTranscriptText(boolean stripColors) { } rowBuffer[column] = c; } - if (mLineWrap[externalToInternalRow(row)]) { - builder.append(rowBuffer, 0, columns); - } else { - builder.append(rowBuffer, 0, lastPrintingChar + 1); - builder.append('\n'); + if( row >= selY1 && row <= selY2 ) { + int x1 = 0; + int x2 = 0; + if( row == selY1 ) + x1 = selX1; + if( row == selY2 ) + x2 = selX2; + else + x2 = columns; + if (mLineWrap[externalToInternalRow(row)]) { + builder.append(rowBuffer, x1, x2 - x1); + } else { + builder.append(rowBuffer, x1, Math.max(0, Math.min(x2 - x1 + 1, lastPrintingChar + 1 - x1))); + builder.append('\n'); + } } } return builder.toString(); @@ -1276,6 +1318,11 @@ class TerminalEmulator { private boolean mAlternateCharSet; + /** + * Used for moving selection up along with the scrolling text + */ + private int mScrollCounter = 0; + /** * Construct a terminal emulator that uses the supplied screen * @@ -2036,6 +2083,8 @@ private void write(byte[] data) { } private void scroll() { + //System.out.println("Scroll(): mTopMargin " + mTopMargin + " mBottomMargin " + mBottomMargin); + mScrollCounter ++; mScreen.scroll(mTopMargin, mBottomMargin, getForeColor(), getBackColor()); } @@ -2205,6 +2254,14 @@ private void setCursorRowCol(int row, int col) { mAboutToAutoWrap = false; } + public int getScrollCounter() { + return mScrollCounter; + } + + public void clearScrollCounter() { + mScrollCounter = 0; + } + /** * Reset the terminal emulator to its initial state. */ @@ -2236,6 +2293,9 @@ public void reset() { public String getTranscriptText() { return mScreen.getTranscriptText(); } + public String getSelectedText(int x1, int y1, int x2, int y2) { + return mScreen.getSelectedText(x1, y1, x2, y2); + } } /** @@ -2649,6 +2709,17 @@ class EmulatorView extends View implements GestureDetector.OnGestureListener { private boolean mCursorVisible = true; + private boolean mIsSelectingText = false; + + private int mSelX1 = -1; + private int mSelY1 = -1; + private int mSelX2 = -1; + private int mSelY2 = -1; + private int mSelX1Old = -1; + private int mSelY1Old = -1; + private int mSelX2Old = -1; + private int mSelY2Old = -1; + /** * Used to poll if the view has changed size. Wish there was a better way to do this. */ @@ -2936,6 +3007,14 @@ public void initialize(FileDescriptor termFd, FileOutputStream termOut) { */ public void append(byte[] buffer, int base, int length) { mEmulator.append(buffer, base, length); + if( mIsSelectingText ) { + int rowShift = mEmulator.getScrollCounter(); + mSelY1 -= rowShift; + mSelY2 -= rowShift; + mSelY1Old -= rowShift; + mSelY2Old -= rowShift; + } + mEmulator.clearScrollCounter(); ensureCursorVisible(); invalidate(); } @@ -2997,6 +3076,15 @@ public boolean onSingleTapUp(MotionEvent e) { } public void onLongPress(MotionEvent e) { + if( mIsSelectingText ) { + if( mSelX1 == mSelX2 && mSelY1 == mSelY2 ) { + mSelX1 = mSelX1Old; + mSelY1 = mSelY1Old; + mSelX2 = mSelX2Old; + mSelY2 = mSelY2Old; + invalidate(); + } + } showContextMenu(); } @@ -3043,6 +3131,30 @@ public void onShowPress(MotionEvent e) { public boolean onDown(MotionEvent e) { mScrollRemainder = 0.0f; + if( mIsSelectingText ) { + if( mSelX1 == mSelX2 && mSelY1 == mSelY2 && mSelX1 != -1 ) { + mSelX2 = (int)(e.getX() / mCharacterWidth); + mSelY2 = (int)(e.getY() / mCharacterHeight) + mTopRow; + int minx = Math.min(mSelX1, mSelX2); + int maxx = Math.max(mSelX1, mSelX2); + int miny = Math.min(mSelY1, mSelY2); + int maxy = Math.max(mSelY1, mSelY2); + mSelX1 = minx; + mSelY1 = miny; + mSelX2 = maxx; + mSelY2 = maxy; + } else { + mSelX1Old = mSelX1; + mSelY1Old = mSelY1; + mSelX2Old = mSelX2; + mSelY2Old = mSelY2; + mSelX1 = (int)(e.getX() / mCharacterWidth); + mSelY1 = (int)(e.getY() / mCharacterHeight) + mTopRow; + mSelX2 = mSelX1; + mSelY2 = mSelY1; + } + invalidate(); + } return true; } @@ -3224,7 +3336,18 @@ protected void onDraw(Canvas canvas) { if (i == cy && mCursorVisible) { cursorX = cx; } - mTranscriptScreen.drawText(i, canvas, x, y, mTextRenderer, cursorX); + int selx1 = -1; + int selx2 = -1; + if( i >= mSelY1 && i <= mSelY2 ) + { + if( i == mSelY1 ) + selx1 = mSelX1; + if( i == mSelY2 ) + selx2 = mSelX2; + else + selx2 = mColumns; + } + mTranscriptScreen.drawText(i, canvas, x, y, mTextRenderer, cursorX, selx1, selx2); y += mCharacterHeight; } } @@ -3241,6 +3364,29 @@ private void ensureCursorVisible() { } } } + + public void toggleSelectingText() { + mIsSelectingText = ! mIsSelectingText; + setVerticalScrollBarEnabled( ! mIsSelectingText ); + if( ! mIsSelectingText ) { + mSelX1 = -1; + mSelY1 = -1; + mSelX2 = -1; + mSelY2 = -1; + mSelX1Old = -1; + mSelY1Old = -1; + mSelX2Old = -1; + mSelY2Old = -1; + } + } + + public boolean getSelectingText() { + return mIsSelectingText; + } + + public String getSelectedText() { + return mEmulator.getSelectedText(mSelX1, mSelY1, mSelX2, mSelY2); + } } From c6aa6d95414c7fbce0ef4139451dbdd33abefb61 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 1 Jan 2011 07:35:40 -0800 Subject: [PATCH 002/847] Update docs for current build environment. --- docs/Building.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/Building.txt b/docs/Building.txt index 32c234c19..d2afa34a7 100644 --- a/docs/Building.txt +++ b/docs/Building.txt @@ -1,7 +1,7 @@ Obtain the Software Needed to Build Android Term ------------------------------------------------ -Android is built using the Android SDK 1.6 or newer and the Android NDK r4 or newer. +Android is built using the Android SDK r08 or newer and the Android NDK r5 or newer. You can downlaod them from: http://developer.android.com/sdk @@ -24,18 +24,18 @@ There are three parts to building Android Term: Build the shared library: - cd + cd /ndk-build (Be sure to use the actual pathnames from your system. For example, on my system I actually type the following: cd ~/code/androidterm - ../android-ndk-r4/ndk-build + ../android-ndk-r5/ndk-build This should chug away for a while and ultimately produce the file -libs/armeabi/libterm.so +libs/armeabi/libandroidterm.so Create the Eclipse project: From 4c8e467e0ea5a9077c80360ad5708f8721c0120f Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 1 Jan 2011 08:05:58 -0800 Subject: [PATCH 003/847] Fix style of text selection patch. --- src/jackpal/androidterm/Term.java | 41 +++++++++++++++++-------------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index b6e2e2582..d43de582c 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -475,8 +475,9 @@ public void onCreateContextMenu(ContextMenu menu, View v, public boolean onContextItemSelected(MenuItem item) { switch (item.getItemId()) { case SELECT_TEXT_ID: - if(mEmulatorView.getSelectingText()) - doCopySelectedText(); + if (mEmulatorView.getSelectingText()) { + doCopySelectedText(); + } mEmulatorView.toggleSelectingText(); return true; case COPY_ALL_ID: @@ -537,8 +538,9 @@ private void doCopySelectedText() { } private void doPaste() { - if(mEmulatorView.getSelectingText()) + if (mEmulatorView.getSelectingText()) { doCopySelectedText(); + } ClipboardManager clip = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); @@ -1076,15 +1078,17 @@ private String internalGetTranscriptText(boolean stripColors, int selX1, int sel } rowBuffer[column] = c; } - if( row >= selY1 && row <= selY2 ) { + if ( row >= selY1 && row <= selY2 ) { int x1 = 0; int x2 = 0; - if( row == selY1 ) + if ( row == selY1 ) { x1 = selX1; - if( row == selY2 ) + } + if ( row == selY2 ) { x2 = selX2; - else + } else { x2 = columns; + } if (mLineWrap[externalToInternalRow(row)]) { builder.append(rowBuffer, x1, x2 - x1); } else { @@ -3007,7 +3011,7 @@ public void initialize(FileDescriptor termFd, FileOutputStream termOut) { */ public void append(byte[] buffer, int base, int length) { mEmulator.append(buffer, base, length); - if( mIsSelectingText ) { + if ( mIsSelectingText ) { int rowShift = mEmulator.getScrollCounter(); mSelY1 -= rowShift; mSelY2 -= rowShift; @@ -3076,8 +3080,8 @@ public boolean onSingleTapUp(MotionEvent e) { } public void onLongPress(MotionEvent e) { - if( mIsSelectingText ) { - if( mSelX1 == mSelX2 && mSelY1 == mSelY2 ) { + if ( mIsSelectingText ) { + if ( mSelX1 == mSelX2 && mSelY1 == mSelY2 ) { mSelX1 = mSelX1Old; mSelY1 = mSelY1Old; mSelX2 = mSelX2Old; @@ -3131,8 +3135,8 @@ public void onShowPress(MotionEvent e) { public boolean onDown(MotionEvent e) { mScrollRemainder = 0.0f; - if( mIsSelectingText ) { - if( mSelX1 == mSelX2 && mSelY1 == mSelY2 && mSelX1 != -1 ) { + if ( mIsSelectingText ) { + if ( mSelX1 == mSelX2 && mSelY1 == mSelY2 && mSelX1 != -1 ) { mSelX2 = (int)(e.getX() / mCharacterWidth); mSelY2 = (int)(e.getY() / mCharacterHeight) + mTopRow; int minx = Math.min(mSelX1, mSelX2); @@ -3338,14 +3342,15 @@ protected void onDraw(Canvas canvas) { } int selx1 = -1; int selx2 = -1; - if( i >= mSelY1 && i <= mSelY2 ) - { - if( i == mSelY1 ) + if ( i >= mSelY1 && i <= mSelY2 ) { + if ( i == mSelY1 ) { selx1 = mSelX1; - if( i == mSelY2 ) + } + if ( i == mSelY2 ) { selx2 = mSelX2; - else + } else { selx2 = mColumns; + } } mTranscriptScreen.drawText(i, canvas, x, y, mTextRenderer, cursorX, selx1, selx2); y += mCharacterHeight; @@ -3368,7 +3373,7 @@ private void ensureCursorVisible() { public void toggleSelectingText() { mIsSelectingText = ! mIsSelectingText; setVerticalScrollBarEnabled( ! mIsSelectingText ); - if( ! mIsSelectingText ) { + if ( ! mIsSelectingText ) { mSelX1 = -1; mSelY1 = -1; mSelX2 = -1; From 497c32a279e7a58cf2842688a1a2204945b80da8 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 1 Jan 2011 09:50:35 -0800 Subject: [PATCH 004/847] Modify text selection algorithm to work better: + Use drag to select rather than two taps. + Text selection is now a spring-loaded mode: It only lasts for one drag session. + Add a verical offset so you can see what you're selecting. --- res/values/strings.xml | 1 - src/jackpal/androidterm/Term.java | 120 +++++++++++++++--------------- 2 files changed, 62 insertions(+), 59 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 30b05febc..a39e26f79 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -24,7 +24,6 @@ Edit text Select text - Copy to clipboard Copy all Paste diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index d43de582c..193aef468 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -188,6 +188,10 @@ public void onCreate(Bundle icicle) { mEmulatorView = (EmulatorView) findViewById(EMULATOR_VIEW); + DisplayMetrics metrics = new DisplayMetrics(); + getWindowManager().getDefaultDisplay().getMetrics(metrics); + mEmulatorView.setScaledDensity(metrics.scaledDensity); + startListening(); mKeyListener = new TermKeyListener(); @@ -463,7 +467,7 @@ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo); menu.setHeaderTitle(R.string.edit_text); - menu.add(0, SELECT_TEXT_ID, 0, !mEmulatorView.getSelectingText() ? R.string.select_text : R.string.select_text_done); + menu.add(0, SELECT_TEXT_ID, 0, R.string.select_text); menu.add(0, COPY_ALL_ID, 0, R.string.copy_all); menu.add(0, PASTE_ID, 0, R.string.paste); if (!canPaste()) { @@ -475,9 +479,6 @@ public void onCreateContextMenu(ContextMenu menu, View v, public boolean onContextItemSelected(MenuItem item) { switch (item.getItemId()) { case SELECT_TEXT_ID: - if (mEmulatorView.getSelectingText()) { - doCopySelectedText(); - } mEmulatorView.toggleSelectingText(); return true; case COPY_ALL_ID: @@ -531,17 +532,7 @@ private void doCopyAll() { clip.setText(mEmulatorView.getTranscriptText().trim()); } - private void doCopySelectedText() { - ClipboardManager clip = (ClipboardManager) - getSystemService(Context.CLIPBOARD_SERVICE); - clip.setText(mEmulatorView.getSelectedText().trim()); - } - private void doPaste() { - if (mEmulatorView.getSelectingText()) { - doCopySelectedText(); - } - ClipboardManager clip = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); CharSequence paste = clip.getText(); @@ -2715,14 +2706,15 @@ class EmulatorView extends View implements GestureDetector.OnGestureListener { private boolean mIsSelectingText = false; + + private float mScaledDensity; + private static final int SELECT_TEXT_OFFSET_Y = -40; + private int mSelXAnchor = -1; + private int mSelYAnchor = -1; private int mSelX1 = -1; private int mSelY1 = -1; private int mSelX2 = -1; private int mSelY2 = -1; - private int mSelX1Old = -1; - private int mSelY1Old = -1; - private int mSelX2Old = -1; - private int mSelY2Old = -1; /** * Used to poll if the view has changed size. Wish there was a better way to do this. @@ -2781,6 +2773,10 @@ public EmulatorView(Context context) { commonConstructor(); } + public void setScaledDensity(float scaledDensity) { + mScaledDensity = scaledDensity; + } + public void onResume() { updateSize(false); mHandler.postDelayed(mCheckSize, SCREEN_CHECK_PERIOD); @@ -3015,8 +3011,7 @@ public void append(byte[] buffer, int base, int length) { int rowShift = mEmulator.getScrollCounter(); mSelY1 -= rowShift; mSelY2 -= rowShift; - mSelY1Old -= rowShift; - mSelY2Old -= rowShift; + mSelYAnchor -= rowShift; } mEmulator.clearScrollCounter(); ensureCursorVisible(); @@ -3080,15 +3075,6 @@ public boolean onSingleTapUp(MotionEvent e) { } public void onLongPress(MotionEvent e) { - if ( mIsSelectingText ) { - if ( mSelX1 == mSelX2 && mSelY1 == mSelY2 ) { - mSelX1 = mSelX1Old; - mSelY1 = mSelY1Old; - mSelX2 = mSelX2Old; - mSelY2 = mSelY2Old; - invalidate(); - } - } showContextMenu(); } @@ -3135,37 +3121,59 @@ public void onShowPress(MotionEvent e) { public boolean onDown(MotionEvent e) { mScrollRemainder = 0.0f; - if ( mIsSelectingText ) { - if ( mSelX1 == mSelX2 && mSelY1 == mSelY2 && mSelX1 != -1 ) { - mSelX2 = (int)(e.getX() / mCharacterWidth); - mSelY2 = (int)(e.getY() / mCharacterHeight) + mTopRow; - int minx = Math.min(mSelX1, mSelX2); - int maxx = Math.max(mSelX1, mSelX2); - int miny = Math.min(mSelY1, mSelY2); - int maxy = Math.max(mSelY1, mSelY2); - mSelX1 = minx; - mSelY1 = miny; - mSelX2 = maxx; - mSelY2 = maxy; - } else { - mSelX1Old = mSelX1; - mSelY1Old = mSelY1; - mSelX2Old = mSelX2; - mSelY2Old = mSelY2; - mSelX1 = (int)(e.getX() / mCharacterWidth); - mSelY1 = (int)(e.getY() / mCharacterHeight) + mTopRow; - mSelX2 = mSelX1; - mSelY2 = mSelY1; - } - invalidate(); - } return true; } // End GestureDetector.OnGestureListener methods @Override public boolean onTouchEvent(MotionEvent ev) { - return mGestureDetector.onTouchEvent(ev); + if (mIsSelectingText) { + return onTouchEventWhileSelectingText(ev); + } else { + return mGestureDetector.onTouchEvent(ev); + } + } + + private boolean onTouchEventWhileSelectingText(MotionEvent ev) { + int action = ev.getAction(); + int cx = (int)(ev.getX() / mCharacterWidth); + int cy = Math.max(0, + (int)((ev.getY() + SELECT_TEXT_OFFSET_Y * mScaledDensity) + / mCharacterHeight) + mTopRow); + switch (action) { + case MotionEvent.ACTION_DOWN: + mSelXAnchor = cx; + mSelYAnchor = cy; + mSelX1 = cx; + mSelY1 = cy; + mSelX2 = mSelX1; + mSelY2 = mSelY1; + break; + case MotionEvent.ACTION_MOVE: + case MotionEvent.ACTION_UP: + int minx = Math.min(mSelXAnchor, cx); + int maxx = Math.max(mSelXAnchor, cx); + int miny = Math.min(mSelYAnchor, cy); + int maxy = Math.max(mSelYAnchor, cy); + mSelX1 = minx; + mSelY1 = miny; + mSelX2 = maxx; + mSelY2 = maxy; + if (action == MotionEvent.ACTION_UP) { + ClipboardManager clip = (ClipboardManager) + getContext().getApplicationContext() + .getSystemService(Context.CLIPBOARD_SERVICE); + clip.setText(getSelectedText().trim()); + toggleSelectingText(); + } + invalidate(); + break; + default: + toggleSelectingText(); + invalidate(); + break; + } + return true; } @Override @@ -3378,10 +3386,6 @@ public void toggleSelectingText() { mSelY1 = -1; mSelX2 = -1; mSelY2 = -1; - mSelX1Old = -1; - mSelY1Old = -1; - mSelX2Old = -1; - mSelY2Old = -1; } } From a0aa6b2c2dd3675f658d51732dc85b8eb3be309f Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 1 Jan 2011 09:57:07 -0800 Subject: [PATCH 005/847] Translation strings courtesy CyanogenMod: https://github.com/CyanogenMod/android_packages_apps_AndroidTerm --- res/values-de/arrays.xml | 70 +++++++++++++++++++++++++++++++++ res/values-de/strings.xml | 72 ++++++++++++++++++++++++++++++++++ res/values-fr/arrays.xml | 57 +++++++++++++++++++++++++++ res/values-fr/strings.xml | 72 ++++++++++++++++++++++++++++++++++ res/values-it/arrays.xml | 71 ++++++++++++++++++++++++++++++++++ res/values-it/strings.xml | 73 +++++++++++++++++++++++++++++++++++ res/values-ja/arrays.xml | 71 ++++++++++++++++++++++++++++++++++ res/values-ja/strings.xml | 73 +++++++++++++++++++++++++++++++++++ res/values-ru/arrays.xml | 60 ++++++++++++++++++++++++++++ res/values-ru/strings.xml | 52 +++++++++++++++++++++++++ res/values-zh-rCN/strings.xml | 73 +++++++++++++++++++++++++++++++++++ 11 files changed, 744 insertions(+) create mode 100644 res/values-de/arrays.xml create mode 100644 res/values-de/strings.xml create mode 100644 res/values-fr/arrays.xml create mode 100644 res/values-fr/strings.xml create mode 100644 res/values-it/arrays.xml create mode 100644 res/values-it/strings.xml create mode 100644 res/values-ja/arrays.xml create mode 100644 res/values-ja/strings.xml create mode 100644 res/values-ru/arrays.xml create mode 100644 res/values-ru/strings.xml create mode 100644 res/values-zh-rCN/strings.xml diff --git a/res/values-de/arrays.xml b/res/values-de/arrays.xml new file mode 100644 index 000000000..1bd3287fd --- /dev/null +++ b/res/values-de/arrays.xml @@ -0,0 +1,70 @@ + + + + + + Statuszeile zeigen + Statuszeile verbergen + + + + Permanenter Cursor + Blinkender Cursor + + + + Rechteck + Unterstrichen + Vertikaler Balken + + + + 4x8 Pixel + 6 Punkte + 7 Punkte + 8 Punkte + 9 Punkte + 10 Punkte + 12 Punkte + 14 Punkte + 16 Punkte + 20 Punkte + + + + Schwarzer Text auf weiss + Weisser Text auf schwarz + Weisser Text auf blau + Grüner Text auf schwarz + Oranger Text auf schwarz + Roter Text auf schwarz + + + + Trackball + \@ Taste + Linke Alt-Taste + Rechte Alt-Taste + Lauter + Leiser + + + + Zeichen-basiert + Wort-basiert + + diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml new file mode 100644 index 000000000..9451a3d01 --- /dev/null +++ b/res/values-de/strings.xml @@ -0,0 +1,72 @@ + + + + Terminal Emulator + Einstellungen + Zurücksetzen + Email an + Spezialtasten + Tastatur an/aus + + Text bearbeiten + Alles kopieren + Einfügen + + + Bildschirm + + Statuszeile + Die Statuszeile anzeigen/verbergen. + Statuszeile + + Aussehen Cursor + Das Aussehen des Cursors auswählen. + Aussehen Cursor + + Cursor-Blinken + Die Art auswählen, wie der Cursor blinken soll. + Cursor-Blinken + + Text + + Schriftgröße + Die Zeichengröße in Punkten auswählen. + Schriftgröße + + Farben + Die Textfarben auswählen. + Textfarbe + + Tastatur + + Steuerungstaste + Die Steuerungstaste auswählen. + Steuerungstaste + + Eingabemethode + Die Eingabemethode für die Soft-Tastatur auswählen. + Eingabemethode + + Shell + Shell + Die zu verwendene Shell auswählen. + Shell + + Startkommando + Kommando eingeben, dass beim Start an die Shell gesendet wird. + Startkommando + diff --git a/res/values-fr/arrays.xml b/res/values-fr/arrays.xml new file mode 100644 index 000000000..a7c51b72c --- /dev/null +++ b/res/values-fr/arrays.xml @@ -0,0 +1,57 @@ + + + + + + Afficher la barre de statut + Masquer la barre de statut + + + + Curseur non clignotant + Curseur clignotant + + + + Rectangle + Souligné + Barre verticale + + + + Texte noir sur blanc + Texte blanc sur noir + Texte blanc sur bleu + Texte vert sur noir + Texte orange sur noir + Texte rouge sur noir + + + + Trackball + Touche \@ + Touche Alt gauche + Touche Alt droite + Touche Vol Haut + Touche Vol Bas + + + + Par caractère + Par mot + + diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml new file mode 100644 index 000000000..e133ae518 --- /dev/null +++ b/res/values-fr/strings.xml @@ -0,0 +1,72 @@ + + + + Terminal Emulator + Préférences + RàZ terminal + Envoyer par e-mail + Touches spéciales + Afficher/Masquer Clavier + + Modifier le texte + Tout copier + Coller + + + Écran + + Barre de statut + Afficher/Masquer la barre de statut + Barre de statut + + Style du curseur + Choisir le style du curseur + Style du curseur + + Clignotement curseur + Choisir si le curseur doit clignoter + Clignotement curseur + + Texte + + Taille police + Choisir la taille de la police de caractères en points + Taille de la police + + Couleurs + Choisir la couleur du texte + Couleur du texte + + Clavier + + Touche CTRL + Choisir quelle touche utiliser pour control (CTRL) + Touche CTRL + + Méthode d\'entrée + Choisir la méthode d\'entrée du clavier virtuel + Méthode d\'entrée + + Shell + Ligne de commande + Régler la ligne de commande du shell + Shell + + Commande initiale + Envoyée au shell au démarrage + Commande initiale + diff --git a/res/values-it/arrays.xml b/res/values-it/arrays.xml new file mode 100644 index 000000000..1c0c48cda --- /dev/null +++ b/res/values-it/arrays.xml @@ -0,0 +1,71 @@ + + + + + + Mostra status bar + Nascondi status bar + + + + Cursore non lampeggiante + Cursore lampeggiante + + + + Rettangolo + Underline + Barra verticale + + + + 4 x 8 pixels + 6 pt + 7 pt + 8 pt + 9 pt + 10 pt + 12 pt + 14 pt + 16 pt + 20 pt + + + + Testo nero su bianco + Testo bianco su nero + Testo bianco su blu + Testo verde su nero + Testo ambra su nero + Testo rosso su nero + + + + Trackball + Tasto \@ + Tasto Alt sin. + Tasto Alt des. + Pulsante Vol + + Pulsante Vol - + + + + Character-based + Word-based + + + diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml new file mode 100644 index 000000000..20fbccc4b --- /dev/null +++ b/res/values-it/strings.xml @@ -0,0 +1,73 @@ + + + + Emulatore Terminale + Preferenze + Resetta term + Invia Email + Combinazione tasti + Attiva/disattiva keyboard + + Modifica testo + Copia tutto + Incolla + + + Schermo + + Status bar + Mostra/nascondi status bar. + Status bar + + Stile cursore + Scegli lo stile del cursore. + Stile cursore + + Lampeggio cursore + Scegli lampeggio cursore. + Lampeggio cursore + + Testo + + Dimensione carattere + Scegli l\'altezza dei caratteri in punti. + Dimensione carattere + + Colori + Scegli il colore del testo. + Colore testo + + Tastierra + + Tasto control + Scegli tasto control. + Tasto control + + Metodo inserimento + Scegli il metodo di inserimento per la tastiera virtuale. + Metodo inserimento + + Shell + Linea di comando + Specifica la shell. + Shell + + Comando iniziale + Inviato alla shell al suo avvio. + Comando iniziale + + diff --git a/res/values-ja/arrays.xml b/res/values-ja/arrays.xml new file mode 100644 index 000000000..939a401fa --- /dev/null +++ b/res/values-ja/arrays.xml @@ -0,0 +1,71 @@ + + + + + + ステータスバーを表示する + ステータスバーを隠す + + + + 点滅しないカーソル + 点滅するカーソル + + + + 四角 + 下線 + 縦線 + + + + 4 x 8ピクセル + 6 pt + 7 pt + 8 pt + 9 pt + 10 pt + 12 pt + 14 pt + 16 pt + 20 pt + + + + 白背景に黒字 + 黒背景に白字 + 青背景に白字 + 黒背景に緑字 + 黒背景にアンバー字 + 黒背景に赤字 + + + + トラックボール + \@キー + 左Altキー + 右Altキー + ボリュームアップ + ボリュームダウン + + + + 文字ベース + 単語ベース + + + diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml new file mode 100644 index 000000000..7c96949ae --- /dev/null +++ b/res/values-ja/strings.xml @@ -0,0 +1,73 @@ + + + + 端末エミュレータ + 設定 + 端末をリセット + メール送信 + 特殊キー + ソフトキーボード + + テキスト編集 + すべてコピー + 貼付け + + + スクリーン + + ステータスバー + ステータスバーの表示/非表示 + ステータスバー + + カーソルのスタイル + カーソルスタイルの選択 + カーソルスタイル + + カーソルの点滅 + カーソルの点滅を選択 + カーソルの点滅 + + テキスト + + フォントサイズ + 文字の高さと大きさを選択 + Font size + + + 文字の色を選択 + 文字の色 + + キーボード + + コントロールキー + コントロールキーを選択 + コントロールキー + + インプットメソッド + ソフトキーボードのインプットメソッドを選択 + インプットメソッド + + シェル + コマンドライン + コマンドラインシェルを指定 + シェル + + 初期コマンド + 開始時、シェルにコマンドを送信する + 初期コマンド + + diff --git a/res/values-ru/arrays.xml b/res/values-ru/arrays.xml new file mode 100644 index 000000000..5be72f1e6 --- /dev/null +++ b/res/values-ru/arrays.xml @@ -0,0 +1,60 @@ + + + + + + Показывать + Скрывать + + + + 4 x 8 пикселей + 6 pt + 7 pt + 8 pt + 9 pt + 10 pt + 12 pt + 14 pt + 16 pt + 20 pt + + + + Чёрный на белом + Белый на чёрном + Белый на синем + Зелёный на чёрном + Жёлтый на чёрном + Красный на чёрном + + + + Jog ball + Клавиша \@ + Левый Alt + Правый Alt + Громкость вверх + Громкость вниз + + + + По знакам + По словам + + + diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml new file mode 100644 index 000000000..ca1fcfb07 --- /dev/null +++ b/res/values-ru/strings.xml @@ -0,0 +1,52 @@ + + + + Эмулятор Терминала + Настройки + Сбросить терм. + Отправит Email + Специальные клавиши + Экранная клавиатура + Изменить + Копировать всё + Вставить + Экран + Статус бар + Показать/Скрыть статус бар. + Статус бар + Текст + Размер шрифта + Выберите размер шрифта. + Размер шрифта + Цвета + Выберите цвет текста. + Цвет текста + Клавиатура + Клавиша Control + Выберите что будет клавишей Control. + Клавиша Control + Способ ввода + Выберите способ ввода для экранной клавиатуры. + Способ ввода + Командная оболочка + Командная строка + Укажите строку обращения к командной оболочки. + Оболочка + Команды запуска + Передаются облочке при запуске терминала. + Команды запуска + diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml new file mode 100644 index 000000000..6f47b4f0b --- /dev/null +++ b/res/values-zh-rCN/strings.xml @@ -0,0 +1,73 @@ + + + + 终端模拟器 + 首选项 + 重置终端 + 发送电子邮件到... + 特殊键 + 打开/关闭软键盘 + + 编辑文本 + 全部复制 + 粘贴 + + + 屏幕 + + 状态栏 + 显示/隐藏状态栏。 + 状态栏 + + 光标样式 + 选择光标样式 + 光标样式 + + 光标闪烁 + 选择光标闪烁模式 + 光标闪烁 + + 文本 + + 文本大小 + 选择文本大小 + 文本大小 + + 颜色 + 选择文本颜色 + 文本颜色 + + 键盘 + + Ctrl 键 + 设置 Ctrl 键 + Ctrl 键 + + 输入方式 + 选择输入方式或软键盘 + 输入方式 + + Shell + 命令行 + 指定命令行使用的 Shell + Shell + + 初始命令 + 启动 Shell 时自动执行的命令 + 初始命令 + + From eec09355f92c0539b34200fdeac9bf1ee7d41376 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 8 Jan 2011 16:15:36 -0800 Subject: [PATCH 006/847] Handle composing text. --- src/jackpal/androidterm/Term.java | 285 ++++++++++++++++++++++-------- 1 file changed, 213 insertions(+), 72 deletions(-) diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index 193aef468..8294ed369 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -60,9 +60,13 @@ import android.view.Window; import android.view.WindowManager; import android.view.inputmethod.BaseInputConnection; +import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.ExtractedText; +import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; +import android.widget.TextView; /** * A terminal emulator activity. @@ -74,6 +78,11 @@ public class Term extends Activity { */ public static final boolean DEBUG = false; + /** + * Set to true to log IME calls. + */ + public static final boolean LOG_IME = DEBUG && false; + /** * Set to true to log each character received from the remote process to the * android log, which makes it easier to debug some kinds of problems with @@ -979,9 +988,10 @@ public void blockSet(int sx, int sy, int w, int h, int val, * @param cx the cursor X coordinate, -1 means don't draw it * @param selx1 the text selection start X coordinate * @param selx2 the text selection end X coordinate, if equals to selx1 don't draw selection + * @param imeText current IME text, to be rendered at cursor */ public final void drawText(int row, Canvas canvas, float x, float y, - TextRenderer renderer, int cx, int selx1, int selx2) { + TextRenderer renderer, int cx, int selx1, int selx2, String imeText) { // Out-of-bounds rows are blank. if (row < -mActiveTranscriptRows || row >= mScreenRows) { @@ -1023,6 +1033,14 @@ public final void drawText(int row, Canvas canvas, float x, float y, (lastColors & CURSOR_MASK) != 0, 0xf & (lastColors >> 12), 0xf & (lastColors >> 8)); } + + if (cx >= 0 && imeText.length() > 0) { + int imeLength = Math.min(columns, imeText.length()); + int imeOffset = imeText.length() - imeLength; + int imePosition = Math.min(cx, columns - imeLength); + renderer.drawTextRun(canvas, x, y, imePosition, imeText.toCharArray(), + imeOffset, imeLength, true, 0x0f, 0x00); + } } /** @@ -2750,6 +2768,8 @@ public void run() { private float mScrollRemainder; private TermKeyListener mKeyListener; + private String mImeBuffer = ""; + /** * Our message handler class. Implements a periodic callback. */ @@ -2822,79 +2842,174 @@ public InputConnection onCreateInputConnection(EditorInfo outAttrs) { outAttrs.inputType = mUseCookedIme ? EditorInfo.TYPE_CLASS_TEXT : EditorInfo.TYPE_NULL; - return new BaseInputConnection(this, false) { + return new InputConnection() { + private boolean mInBatchEdit; + /** + * Used to handle composing text requests + */ + private int mCursor; + private int mComposingTextStart; + private int mComposingTextEnd; - @Override - public boolean commitText(CharSequence text, int newCursorPosition) { - sendText(text); + private void sendChar(int c) { + try { + mapAndSend(c); + } catch (IOException ex) { + + } + } + + private void sendText(CharSequence text) { + int n = text.length(); + try { + for(int i = 0; i < n; i++) { + char c = text.charAt(i); + mapAndSend(c); + } + mTermOut.flush(); + } catch (IOException e) { + Log.e(TAG, "error writing ", e); + } + } + + private void mapAndSend(int c) throws IOException { + mTermOut.write( + mKeyListener.mapControlChar(c)); + } + + public boolean beginBatchEdit() { + if (Term.LOG_IME) { + Log.w(TAG, "beginBatchEdit"); + } + setImeBuffer(""); + mCursor = 0; + mComposingTextStart = 0; + mComposingTextEnd = 0; + mInBatchEdit = true; return true; } - @Override - public boolean performEditorAction(int actionCode) { - if(actionCode == EditorInfo.IME_ACTION_UNSPECIFIED) { - // The "return" key has been pressed on the IME. - sendText("\n"); - return true; + public boolean clearMetaKeyStates(int arg0) { + if (Term.LOG_IME) { + Log.w(TAG, "clearMetaKeyStates " + arg0); } return false; } - @Override - public boolean sendKeyEvent(KeyEvent event) { - if (event.getAction() == KeyEvent.ACTION_DOWN) { - // Some keys are sent here rather than to commitText. - // In particular, del and the digit keys are sent here. - // (And I have reports that the HTC Magic also sends Return here.) - // As a bit of defensive programming, handle every - // key with an ASCII meaning. - int keyCode = event.getKeyCode(); - if (keyCode >= 0 && keyCode < KEYCODE_CHARS.length()) { - char c = KEYCODE_CHARS.charAt(keyCode); - if (c > 0) { - sendChar(c); - } else { - // Handle IME arrow key events - switch (keyCode) { - case KeyEvent.KEYCODE_DPAD_UP: // Up Arrow - case KeyEvent.KEYCODE_DPAD_DOWN: // Down Arrow - case KeyEvent.KEYCODE_DPAD_LEFT: // Left Arrow - case KeyEvent.KEYCODE_DPAD_RIGHT: // Right Arrow - super.sendKeyEvent(event); - break; - default: - break; - } // switch (keyCode) - } - } + public boolean commitCompletion(CompletionInfo arg0) { + if (Term.LOG_IME) { + Log.w(TAG, "commitCompletion " + arg0); } + return false; + } + + public boolean endBatchEdit() { + if (Term.LOG_IME) { + Log.w(TAG, "endBatchEdit"); + } + mInBatchEdit = false; return true; } - private final String KEYCODE_CHARS = - "\000\000\000\000\000\000\000" + "0123456789*#" - + "\000\000\000\000\000\000\000\000\000\000" - + "abcdefghijklmnopqrstuvwxyz,." - + "\000\000\000\000" - + "\011 " // tab, space - + "\000\000\000" // sym .. envelope - + "\015\177" // enter, del - + "`-=[]\\;'/@" - + "\000\000" - + "\000+"; + public boolean finishComposingText() { + if (Term.LOG_IME) { + Log.w(TAG, "finishComposingText"); + } + sendText(mImeBuffer); + setImeBuffer(""); + mComposingTextStart = 0; + mComposingTextEnd = 0; + mCursor = 0; + return true; + } - @Override - public boolean setComposingText(CharSequence text, int newCursorPosition) { + public int getCursorCapsMode(int arg0) { + if (Term.LOG_IME) { + Log.w(TAG, "getCursorCapsMode(" + arg0 + ")"); + } + return 0; + } + + public ExtractedText getExtractedText(ExtractedTextRequest arg0, + int arg1) { + if (Term.LOG_IME) { + Log.w(TAG, "getExtractedText" + arg0 + "," + arg1); + } + return null; + } + + public CharSequence getTextAfterCursor(int n, int flags) { + if (Term.LOG_IME) { + Log.w(TAG, "getTextAfterCursor(" + n + "," + flags + ")"); + } + int len = Math.min(n, mImeBuffer.length() - mCursor); + if (len <= 0 || mCursor < 0 || mCursor >= mImeBuffer.length()) { + return ""; + } + return mImeBuffer.substring(mCursor, mCursor + len); + } + + public CharSequence getTextBeforeCursor(int n, int flags) { + if (Term.LOG_IME) { + Log.w(TAG, "getTextBeforeCursor(" + n + "," + flags + ")"); + } + int len = Math.min(n, mCursor); + if (len <= 0 || mCursor < 0 || mCursor >= mImeBuffer.length()) { + return ""; + } + return mImeBuffer.substring(mCursor-len, mCursor); + } + + public boolean performContextMenuAction(int arg0) { + if (Term.LOG_IME) { + Log.w(TAG, "performContextMenuAction" + arg0); + } return true; } - @Override - public boolean setSelection(int start, int end) { + public boolean performPrivateCommand(String arg0, Bundle arg1) { + if (Term.LOG_IME) { + Log.w(TAG, "performPrivateCommand" + arg0 + "," + arg1); + } return true; } - @Override + public boolean reportFullscreenMode(boolean arg0) { + if (Term.LOG_IME) { + Log.w(TAG, "reportFullscreenMode" + arg0); + } + return true; + } + + public boolean commitText(CharSequence text, int newCursorPosition) { + if (Term.LOG_IME) { + Log.w(TAG, "commitText(\"" + text + "\", " + newCursorPosition + ")"); + } + clearComposingText(); + sendText(text); + setImeBuffer(""); + mCursor = 0; + return true; + } + + private void clearComposingText() { + setImeBuffer(mImeBuffer.substring(0, mComposingTextStart) + + mImeBuffer.substring(mComposingTextEnd)); + if (mCursor < mComposingTextStart) { + // do nothing + } else if (mCursor < mComposingTextEnd) { + mCursor = mComposingTextStart; + } else { + mCursor -= mComposingTextEnd - mComposingTextStart; + } + mComposingTextEnd = mComposingTextStart = 0; + } + public boolean deleteSurroundingText(int leftLength, int rightLength) { + if (Term.LOG_IME) { + Log.w(TAG, "deleteSurroundingText(" + leftLength + + "," + rightLength + ")"); + } if (leftLength > 0) { for (int i = 0; i < leftLength; i++) { sendKeyEvent( @@ -2909,31 +3024,57 @@ public boolean deleteSurroundingText(int leftLength, int rightLength) { return true; } - private void sendChar(int c) { - try { - mapAndSend(c); - } catch (IOException ex) { + public boolean performEditorAction(int actionCode) { + if (Term.LOG_IME) { + Log.w(TAG, "performEditorAction(" + actionCode + ")"); + } + if (actionCode == EditorInfo.IME_ACTION_UNSPECIFIED) { + // The "return" key has been pressed on the IME. + sendText("\n"); + } + return true; + } + public boolean sendKeyEvent(KeyEvent event) { + if (Term.LOG_IME) { + Log.w(TAG, "sendKeyEvent(" + event + ")"); } + // Some keys are sent here rather than to commitText. + // In particular, del and the digit keys are sent here. + // (And I have reports that the HTC Magic also sends Return here.) + // As a bit of defensive programming, handle every key. + dispatchKeyEvent(event); + return true; } - private void sendText(CharSequence text) { - int n = text.length(); - try { - for(int i = 0; i < n; i++) { - char c = text.charAt(i); - mapAndSend(c); - } - } catch (IOException e) { + + public boolean setComposingText(CharSequence text, int newCursorPosition) { + if (Term.LOG_IME) { + Log.w(TAG, "setComposingText(\"" + text + "\", " + newCursorPosition + ")"); } + setImeBuffer(mImeBuffer.substring(0, mComposingTextStart) + + text + mImeBuffer.substring(mComposingTextEnd)); + mComposingTextEnd = mComposingTextStart + text.length(); + mCursor = newCursorPosition > 0 ? mComposingTextEnd + newCursorPosition - 1 + : mComposingTextStart - newCursorPosition; + return true; } - private void mapAndSend(int c) throws IOException { - mTermOut.write( - mKeyListener.mapControlChar(c)); + public boolean setSelection(int arg0, int arg1) { + if (Term.LOG_IME) { + Log.w(TAG, "setSelection" + arg0 + "," + arg1); + } + return true; } }; } + private void setImeBuffer(String buffer) { + if (!buffer.equals(mImeBuffer)) { + invalidate(); + } + mImeBuffer = buffer; + } + public boolean getKeypadApplicationMode() { return mEmulator.getKeypadApplicationMode(); } @@ -3064,8 +3205,8 @@ public void setCursorStyle(int style, int blink) { mCursorBlink = blink; } - public void setUseCookedIME(boolean useRawIME) { - mUseCookedIme = useRawIME; + public void setUseCookedIME(boolean useCookedIME) { + mUseCookedIme = useCookedIME; } // Begin GestureDetector.OnGestureListener methods @@ -3360,7 +3501,7 @@ protected void onDraw(Canvas canvas) { selx2 = mColumns; } } - mTranscriptScreen.drawText(i, canvas, x, y, mTextRenderer, cursorX, selx1, selx2); + mTranscriptScreen.drawText(i, canvas, x, y, mTextRenderer, cursorX, selx1, selx2, mImeBuffer); y += mCharacterHeight; } } From cb560e765aecf54c31dde2ed85696f07c5d0b9ef Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 8 Jan 2011 16:15:46 -0800 Subject: [PATCH 007/847] Bump version number. --- AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index a0551beff..6cc87f41a 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,6 +1,6 @@ + android:versionName="1.0.20" android:versionCode="21"> Date: Sat, 8 Jan 2011 16:37:29 -0800 Subject: [PATCH 008/847] We don't require a touch screen, even though we use the feature. --- AndroidManifest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 6cc87f41a..8c271a7b7 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,6 +1,7 @@ + Date: Sat, 8 Jan 2011 17:07:11 -0800 Subject: [PATCH 009/847] Add a white background layer. --- docs/Market Icon.psd | Bin 260338 -> 277317 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/Market Icon.psd b/docs/Market Icon.psd index 83d61fce693d952e0374addd4a218df516f45a75..90b6f7723e9297dc2ccc2f3ef3d65502908d81a0 100644 GIT binary patch delta 763 zcmezLk^ks7fglT~fM5nj1_)qgU|<5WfS7{;$OeIu6BbUMzAP+Y4!aCc3Zw!orV17l zc=0jjv;IMeg^Q}w*B(u|x@)7|7A65hT>~Jp2r)FXGBL0+u$;`qY=$AY*@KylQNhf` z)zs9*z{17C(Am|%(9p@i(aFio!otFM>7LMGdFV^{gTYI$qAhCn{Tq^dvCrHWXxy{aVE&U3=CXg5=?*{ZvbYp zGJ*g^oi12}4ICIelkGyxjlF>)Y+z9^0Wt_|7&n72Ln1>FLpDPyLkWWdgC|2WLq0$l-bUMY;ch(E|hzCNC&8aNu%EOwLX($}i1J0sAATG%ph< zDFMV>3{F6ICIj7>4s>lkLn+XWDIh~~Dl=1n@}NlNnatH(yg9RUg4Ohge9T{_zvp9q zhD5}3Ge>UslV+aBv~51~BNh~54-?RJ(;x6Le?`&&)<1Z3ZT_}?U3dE;YsT%1teMu_ J26@HxFaT+?%N+m! delta 8077 zcmeHMPe>F|82{ePI*FyOTZROQmk@%5nf>c75xANl5u|QP(nZum5F%?u=HamvSccXz zMFk$ZEL2{MiKKHBD0}N72zKch>8>IO;_I8YV{Yw`mz;N*S>DY1es5;JKX2yy_WkDb zmbZ|vJ>QbJ+yexPn+HI`9=1x5i_oojOQQ3V=M0y;MQnfnuKT^@RsEXg4+N5GBpwXM zgPML)j+BAMHCZ85iHI5ssF6@8tT%WXjVaGJ(iCn}Tcd5ESbHoHZI8r49g$Et8tDkO zMq4_xfYxq2RK8xM@=va{`dBhGFfukekQyEt90zV&Js)YreKjN@1p_bwV=#)x6bxg3 z5XOO@DX;M}YXBJIJ%AxnyhoVJb-2e^l#QN-Bjz}U$LQ^jk*LJ=nxQfyWo;Z&x!@tV zaoNvhg-6(%Y?zP<+IF__){MV_#8^od*5Y{M`?CN^iqMon9|AbZmxBiJOKyLjLTY%j9=hi=wOAvScCw2km)<3uYIlBab zQigDYum7QxMqD=k4?;QBSVl(@Wo4hUTt&|E4`S2gHIez8Hq*P=V7h>^Bu{MrCP%j? zK&FMiNz}7Qp{{`}Ctv5D^J^zrMQ$$1`PTpci2%Pp2xZ_+{#m36=>I|V_n{g{#fBeF H*m?I0g#ary From 104a92911221c953ca1fe7dc6708f0ba431d58e9 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 8 Jan 2011 17:08:41 -0800 Subject: [PATCH 010/847] Ignore a png file that's a documentation build artifact. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index bcc5997c2..4ce94ab5f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ bin/ +docs/Market Icon.png gen/ libs/ obj/ From ce9b91f02d07a44f1c35887005a6fe89b9984fd9 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sun, 9 Jan 2011 10:31:17 -0800 Subject: [PATCH 011/847] Add '|' and arrow keys to "Special Chars" Add 'Camera' button as a possible Control key Make 'Volume Down' the default control key. --- AndroidManifest.xml | 3 +- res/values/arrays.xml | 2 + res/values/styles.xml | 4 +- src/jackpal/androidterm/Term.java | 94 ++++++++++++++++++++----------- 4 files changed, 67 insertions(+), 36 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 8c271a7b7..0a12c808c 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,7 +1,6 @@ - + android:versionName="1.0.21" android:versionCode="22"> Right Alt key Vol Up key Vol Down key + Camera key @@ -114,6 +115,7 @@ 3 4 5 + 6 diff --git a/res/values/styles.xml b/res/values/styles.xml index 0e59f4a76..213c38d99 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -4,9 +4,9 @@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - + http://www.apache.org/licenses/LICENSE-2.0 - + Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index 8294ed369..36be5ec15 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -134,7 +134,7 @@ public class Term extends Activity { private int mCursorBlink = 0; private int mFontSize = 9; private int mColorId = 2; - private int mControlKeyId = 0; + private int mControlKeyId = 5; // Default to Volume Down private int mUseCookedIME = 0; private static final String STATUSBAR_KEY = "statusbar"; @@ -163,10 +163,11 @@ public class Term extends Activity { KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_ALT_RIGHT, KeyEvent.KEYCODE_VOLUME_UP, - KeyEvent.KEYCODE_VOLUME_DOWN + KeyEvent.KEYCODE_VOLUME_DOWN, + KeyEvent.KEYCODE_CAMERA }; private static final String[] CONTROL_KEY_NAME = { - "Ball", "@", "Left-Alt", "Right-Alt", "Vol-Up", "Vol-Dn" + "Ball", "@", "Left-Alt", "Right-Alt", "Vol-Up", "Vol-Dn", "Camera" }; private int mControlKeyCode; @@ -563,14 +564,19 @@ private void doDocumentKeys() { String controlKey = CONTROL_KEY_NAME[mControlKeyId]; new AlertDialog.Builder(this). setTitle("Press " + controlKey + " and Key"). - setMessage(controlKey + " Space ==> Control-@ (NUL)\n" - + controlKey + " A..Z ==> Control-A..Z\n" - + controlKey + " 1 ==> Control-[ (ESC)\n" - + controlKey + " 5 ==> Control-_\n" - + controlKey + " . ==> Control-\\\n" - + controlKey + " 0 ==> Control-]\n" - + controlKey + " 6 ==> Control-^"). - show(); + setMessage(controlKey + " Space : Control-@ (NUL)\n" + + controlKey + " A..Z : Control-A..Z\n" + + controlKey + " 1 : Control-[ (ESC)\n" + + controlKey + " 2 : Control-_\n" + + controlKey + " 3 : Control-^\n" + + controlKey + " 4 : Control-]\n" + + controlKey + " 5 : | (pipe)\n" + + controlKey + " 6 : Left arrow\n" + + controlKey + " 7 : Down arrow\n" + + controlKey + " 8 : Up arrow\n" + + controlKey + " 9 : Right arrow\n" + + controlKey + " . : Control-\\" + ).show(); } private void doToggleSoftKeyboard() { @@ -2873,8 +2879,10 @@ private void sendText(CharSequence text) { } private void mapAndSend(int c) throws IOException { - mTermOut.write( - mKeyListener.mapControlChar(c)); + int result = mKeyListener.mapControlChar(c); + if (result < TermKeyListener.KEYCODE_OFFSET) { + mTermOut.write(result); + } } public boolean beginBatchEdit() { @@ -4187,6 +4195,8 @@ public boolean isActive() { private boolean mCapsLock; + static public final int KEYCODE_OFFSET = 1000; + /** * Construct a term key listener. * @@ -4215,12 +4225,22 @@ public int mapControlChar(int ch) { result = 27; } else if ((result == '\\') || (result == '.')) { result = 28; - } else if ((result == ']') || (result == '0')) { + } else if ((result == ']') || (result == '4')) { result = 29; - } else if ((result == '^') || (result == '6')) { + } else if ((result == '^') || (result == '3')) { result = 30; // control-^ - } else if ((result == '_') || (result == '5')) { + } else if ((result == '_') || (result == '2')) { result = 31; + } else if ((result == '5')) { + result = '|'; + } else if ((result == '6')) { + result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_LEFT; + } else if ((result == '7')) { + result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_DOWN; + } else if ((result == '8')) { + result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_UP; + } else if ((result == '9')) { + result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_RIGHT; } } @@ -4229,6 +4249,7 @@ public int mapControlChar(int ch) { mCapKey.adjustAfterKeypress(); mControlKey.adjustAfterKeypress(); } + return result; } @@ -4239,21 +4260,8 @@ public int mapControlChar(int ch) { * */ public void keyDown(int keyCode, KeyEvent event, OutputStream out, boolean appMode) throws IOException { - if (keyCode >= 0 && keyCode < mKeyCodes.length) { - String code = null; - if (appMode) { - code = mAppKeyCodes[keyCode]; - } - if (code == null) { - code = mKeyCodes[keyCode]; - } - if (code != null) { - int length = code.length(); - for (int i = 0; i < length; i++) { - out.write(code.charAt(i)); - } - return; - } + if (handleKeyCode(keyCode, out, appMode)) { + return; } int result = -1; switch (keyCode) { @@ -4288,11 +4296,33 @@ public void keyDown(int keyCode, KeyEvent event, OutputStream out, boolean appMo result = mapControlChar(result); - if (result >= 0) { + if (result >= KEYCODE_OFFSET) { + handleKeyCode(result - KEYCODE_OFFSET, out, appMode); + } else if (result >= 0) { out.write(result); } } + private boolean handleKeyCode(int keyCode, OutputStream out, boolean appMode) throws IOException { + if (keyCode >= 0 && keyCode < mKeyCodes.length) { + String code = null; + if (appMode) { + code = mAppKeyCodes[keyCode]; + } + if (code == null) { + code = mKeyCodes[keyCode]; + } + if (code != null) { + int length = code.length(); + for (int i = 0; i < length; i++) { + out.write(code.charAt(i)); + } + return true; + } + } + return false; + } + /** * Handle a keyUp event. * From 988c8a4aaf515a2d03bbc432b212b23dbd476bac Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 8 Jan 2011 16:15:36 -0800 Subject: [PATCH 012/847] Handle composing text. --- src/jackpal/androidterm/Term.java | 285 ++++++++++++++++++++++-------- 1 file changed, 213 insertions(+), 72 deletions(-) diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index 3bb011ce0..36e970cd7 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -60,9 +60,13 @@ import android.view.Window; import android.view.WindowManager; import android.view.inputmethod.BaseInputConnection; +import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.ExtractedText; +import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; +import android.widget.TextView; /** * A terminal emulator activity. @@ -74,6 +78,11 @@ public class Term extends Activity { */ public static final boolean DEBUG = false; + /** + * Set to true to log IME calls. + */ + public static final boolean LOG_IME = DEBUG && false; + /** * Set to true to log each character received from the remote process to the * android log, which makes it easier to debug some kinds of problems with @@ -979,9 +988,10 @@ public void blockSet(int sx, int sy, int w, int h, int val, * @param cx the cursor X coordinate, -1 means don't draw it * @param selx1 the text selection start X coordinate * @param selx2 the text selection end X coordinate, if equals to selx1 don't draw selection + * @param imeText current IME text, to be rendered at cursor */ public final void drawText(int row, Canvas canvas, float x, float y, - TextRenderer renderer, int cx, int selx1, int selx2) { + TextRenderer renderer, int cx, int selx1, int selx2, String imeText) { // Out-of-bounds rows are blank. if (row < -mActiveTranscriptRows || row >= mScreenRows) { @@ -1023,6 +1033,14 @@ public final void drawText(int row, Canvas canvas, float x, float y, (lastColors & CURSOR_MASK) != 0, 0xf & (lastColors >> 12), 0xf & (lastColors >> 8)); } + + if (cx >= 0 && imeText.length() > 0) { + int imeLength = Math.min(columns, imeText.length()); + int imeOffset = imeText.length() - imeLength; + int imePosition = Math.min(cx, columns - imeLength); + renderer.drawTextRun(canvas, x, y, imePosition, imeText.toCharArray(), + imeOffset, imeLength, true, 0x0f, 0x00); + } } /** @@ -2750,6 +2768,8 @@ public void run() { private float mScrollRemainder; private TermKeyListener mKeyListener; + private String mImeBuffer = ""; + /** * Our message handler class. Implements a periodic callback. */ @@ -2822,79 +2842,174 @@ public InputConnection onCreateInputConnection(EditorInfo outAttrs) { outAttrs.inputType = mUseCookedIme ? EditorInfo.TYPE_CLASS_TEXT : EditorInfo.TYPE_NULL; - return new BaseInputConnection(this, false) { + return new InputConnection() { + private boolean mInBatchEdit; + /** + * Used to handle composing text requests + */ + private int mCursor; + private int mComposingTextStart; + private int mComposingTextEnd; - @Override - public boolean commitText(CharSequence text, int newCursorPosition) { - sendText(text); + private void sendChar(int c) { + try { + mapAndSend(c); + } catch (IOException ex) { + + } + } + + private void sendText(CharSequence text) { + int n = text.length(); + try { + for(int i = 0; i < n; i++) { + char c = text.charAt(i); + mapAndSend(c); + } + mTermOut.flush(); + } catch (IOException e) { + Log.e(TAG, "error writing ", e); + } + } + + private void mapAndSend(int c) throws IOException { + mTermOut.write( + mKeyListener.mapControlChar(c)); + } + + public boolean beginBatchEdit() { + if (Term.LOG_IME) { + Log.w(TAG, "beginBatchEdit"); + } + setImeBuffer(""); + mCursor = 0; + mComposingTextStart = 0; + mComposingTextEnd = 0; + mInBatchEdit = true; return true; } - @Override - public boolean performEditorAction(int actionCode) { - if(actionCode == EditorInfo.IME_ACTION_UNSPECIFIED) { - // The "return" key has been pressed on the IME. - sendText("\n"); - return true; + public boolean clearMetaKeyStates(int arg0) { + if (Term.LOG_IME) { + Log.w(TAG, "clearMetaKeyStates " + arg0); } return false; } - @Override - public boolean sendKeyEvent(KeyEvent event) { - if (event.getAction() == KeyEvent.ACTION_DOWN) { - // Some keys are sent here rather than to commitText. - // In particular, del and the digit keys are sent here. - // (And I have reports that the HTC Magic also sends Return here.) - // As a bit of defensive programming, handle every - // key with an ASCII meaning. - int keyCode = event.getKeyCode(); - if (keyCode >= 0 && keyCode < KEYCODE_CHARS.length()) { - char c = KEYCODE_CHARS.charAt(keyCode); - if (c > 0) { - sendChar(c); - } else { - // Handle IME arrow key events - switch (keyCode) { - case KeyEvent.KEYCODE_DPAD_UP: // Up Arrow - case KeyEvent.KEYCODE_DPAD_DOWN: // Down Arrow - case KeyEvent.KEYCODE_DPAD_LEFT: // Left Arrow - case KeyEvent.KEYCODE_DPAD_RIGHT: // Right Arrow - super.sendKeyEvent(event); - break; - default: - break; - } // switch (keyCode) - } - } + public boolean commitCompletion(CompletionInfo arg0) { + if (Term.LOG_IME) { + Log.w(TAG, "commitCompletion " + arg0); } + return false; + } + + public boolean endBatchEdit() { + if (Term.LOG_IME) { + Log.w(TAG, "endBatchEdit"); + } + mInBatchEdit = false; return true; } - private final String KEYCODE_CHARS = - "\000\000\000\000\000\000\000" + "0123456789*#" - + "\000\000\000\000\000\000\000\000\000\000" - + "abcdefghijklmnopqrstuvwxyz,." - + "\000\000\000\000" - + "\011 " // tab, space - + "\000\000\000" // sym .. envelope - + "\015\177" // enter, del - + "`-=[]\\;'/@" - + "\000\000" - + "\000+"; + public boolean finishComposingText() { + if (Term.LOG_IME) { + Log.w(TAG, "finishComposingText"); + } + sendText(mImeBuffer); + setImeBuffer(""); + mComposingTextStart = 0; + mComposingTextEnd = 0; + mCursor = 0; + return true; + } - @Override - public boolean setComposingText(CharSequence text, int newCursorPosition) { + public int getCursorCapsMode(int arg0) { + if (Term.LOG_IME) { + Log.w(TAG, "getCursorCapsMode(" + arg0 + ")"); + } + return 0; + } + + public ExtractedText getExtractedText(ExtractedTextRequest arg0, + int arg1) { + if (Term.LOG_IME) { + Log.w(TAG, "getExtractedText" + arg0 + "," + arg1); + } + return null; + } + + public CharSequence getTextAfterCursor(int n, int flags) { + if (Term.LOG_IME) { + Log.w(TAG, "getTextAfterCursor(" + n + "," + flags + ")"); + } + int len = Math.min(n, mImeBuffer.length() - mCursor); + if (len <= 0 || mCursor < 0 || mCursor >= mImeBuffer.length()) { + return ""; + } + return mImeBuffer.substring(mCursor, mCursor + len); + } + + public CharSequence getTextBeforeCursor(int n, int flags) { + if (Term.LOG_IME) { + Log.w(TAG, "getTextBeforeCursor(" + n + "," + flags + ")"); + } + int len = Math.min(n, mCursor); + if (len <= 0 || mCursor < 0 || mCursor >= mImeBuffer.length()) { + return ""; + } + return mImeBuffer.substring(mCursor-len, mCursor); + } + + public boolean performContextMenuAction(int arg0) { + if (Term.LOG_IME) { + Log.w(TAG, "performContextMenuAction" + arg0); + } return true; } - @Override - public boolean setSelection(int start, int end) { + public boolean performPrivateCommand(String arg0, Bundle arg1) { + if (Term.LOG_IME) { + Log.w(TAG, "performPrivateCommand" + arg0 + "," + arg1); + } return true; } - @Override + public boolean reportFullscreenMode(boolean arg0) { + if (Term.LOG_IME) { + Log.w(TAG, "reportFullscreenMode" + arg0); + } + return true; + } + + public boolean commitText(CharSequence text, int newCursorPosition) { + if (Term.LOG_IME) { + Log.w(TAG, "commitText(\"" + text + "\", " + newCursorPosition + ")"); + } + clearComposingText(); + sendText(text); + setImeBuffer(""); + mCursor = 0; + return true; + } + + private void clearComposingText() { + setImeBuffer(mImeBuffer.substring(0, mComposingTextStart) + + mImeBuffer.substring(mComposingTextEnd)); + if (mCursor < mComposingTextStart) { + // do nothing + } else if (mCursor < mComposingTextEnd) { + mCursor = mComposingTextStart; + } else { + mCursor -= mComposingTextEnd - mComposingTextStart; + } + mComposingTextEnd = mComposingTextStart = 0; + } + public boolean deleteSurroundingText(int leftLength, int rightLength) { + if (Term.LOG_IME) { + Log.w(TAG, "deleteSurroundingText(" + leftLength + + "," + rightLength + ")"); + } if (leftLength > 0) { for (int i = 0; i < leftLength; i++) { sendKeyEvent( @@ -2909,31 +3024,57 @@ public boolean deleteSurroundingText(int leftLength, int rightLength) { return true; } - private void sendChar(int c) { - try { - mapAndSend(c); - } catch (IOException ex) { + public boolean performEditorAction(int actionCode) { + if (Term.LOG_IME) { + Log.w(TAG, "performEditorAction(" + actionCode + ")"); + } + if (actionCode == EditorInfo.IME_ACTION_UNSPECIFIED) { + // The "return" key has been pressed on the IME. + sendText("\n"); + } + return true; + } + public boolean sendKeyEvent(KeyEvent event) { + if (Term.LOG_IME) { + Log.w(TAG, "sendKeyEvent(" + event + ")"); } + // Some keys are sent here rather than to commitText. + // In particular, del and the digit keys are sent here. + // (And I have reports that the HTC Magic also sends Return here.) + // As a bit of defensive programming, handle every key. + dispatchKeyEvent(event); + return true; } - private void sendText(CharSequence text) { - int n = text.length(); - try { - for(int i = 0; i < n; i++) { - char c = text.charAt(i); - mapAndSend(c); - } - } catch (IOException e) { + + public boolean setComposingText(CharSequence text, int newCursorPosition) { + if (Term.LOG_IME) { + Log.w(TAG, "setComposingText(\"" + text + "\", " + newCursorPosition + ")"); } + setImeBuffer(mImeBuffer.substring(0, mComposingTextStart) + + text + mImeBuffer.substring(mComposingTextEnd)); + mComposingTextEnd = mComposingTextStart + text.length(); + mCursor = newCursorPosition > 0 ? mComposingTextEnd + newCursorPosition - 1 + : mComposingTextStart - newCursorPosition; + return true; } - private void mapAndSend(int c) throws IOException { - mTermOut.write( - mKeyListener.mapControlChar(c)); + public boolean setSelection(int arg0, int arg1) { + if (Term.LOG_IME) { + Log.w(TAG, "setSelection" + arg0 + "," + arg1); + } + return true; } }; } + private void setImeBuffer(String buffer) { + if (!buffer.equals(mImeBuffer)) { + invalidate(); + } + mImeBuffer = buffer; + } + public boolean getKeypadApplicationMode() { return mEmulator.getKeypadApplicationMode(); } @@ -3064,8 +3205,8 @@ public void setCursorStyle(int style, int blink) { mCursorBlink = blink; } - public void setUseCookedIME(boolean useRawIME) { - mUseCookedIme = useRawIME; + public void setUseCookedIME(boolean useCookedIME) { + mUseCookedIme = useCookedIME; } // Begin GestureDetector.OnGestureListener methods @@ -3360,7 +3501,7 @@ protected void onDraw(Canvas canvas) { selx2 = mColumns; } } - mTranscriptScreen.drawText(i, canvas, x, y, mTextRenderer, cursorX, selx1, selx2); + mTranscriptScreen.drawText(i, canvas, x, y, mTextRenderer, cursorX, selx1, selx2, mImeBuffer); y += mCharacterHeight; } } From 5e7a9b0f164848faefd4f4937b1a1d12edff60c7 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 8 Jan 2011 16:15:46 -0800 Subject: [PATCH 013/847] Bump version number. Change-Id: Ie79439aa03bfd6c2c69fe171b94c00dc4329c2b2 --- AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index fff51653d..91879fa4c 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,6 +1,6 @@ + android:versionName="1.0.20" android:versionCode="21"> Date: Sat, 8 Jan 2011 16:37:29 -0800 Subject: [PATCH 014/847] We don't require a touch screen, even though we use the feature. --- AndroidManifest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 91879fa4c..f90187982 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,6 +1,7 @@ + Date: Sat, 8 Jan 2011 17:07:11 -0800 Subject: [PATCH 015/847] Add a white background layer. --- docs/Market Icon.psd | Bin 260338 -> 277317 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/Market Icon.psd b/docs/Market Icon.psd index 83d61fce693d952e0374addd4a218df516f45a75..90b6f7723e9297dc2ccc2f3ef3d65502908d81a0 100644 GIT binary patch delta 763 zcmezLk^ks7fglT~fM5nj1_)qgU|<5WfS7{;$OeIu6BbUMzAP+Y4!aCc3Zw!orV17l zc=0jjv;IMeg^Q}w*B(u|x@)7|7A65hT>~Jp2r)FXGBL0+u$;`qY=$AY*@KylQNhf` z)zs9*z{17C(Am|%(9p@i(aFio!otFM>7LMGdFV^{gTYI$qAhCn{Tq^dvCrHWXxy{aVE&U3=CXg5=?*{ZvbYp zGJ*g^oi12}4ICIelkGyxjlF>)Y+z9^0Wt_|7&n72Ln1>FLpDPyLkWWdgC|2WLq0$l-bUMY;ch(E|hzCNC&8aNu%EOwLX($}i1J0sAATG%ph< zDFMV>3{F6ICIj7>4s>lkLn+XWDIh~~Dl=1n@}NlNnatH(yg9RUg4Ohge9T{_zvp9q zhD5}3Ge>UslV+aBv~51~BNh~54-?RJ(;x6Le?`&&)<1Z3ZT_}?U3dE;YsT%1teMu_ J26@HxFaT+?%N+m! delta 8077 zcmeHMPe>F|82{ePI*FyOTZROQmk@%5nf>c75xANl5u|QP(nZum5F%?u=HamvSccXz zMFk$ZEL2{MiKKHBD0}N72zKch>8>IO;_I8YV{Yw`mz;N*S>DY1es5;JKX2yy_WkDb zmbZ|vJ>QbJ+yexPn+HI`9=1x5i_oojOQQ3V=M0y;MQnfnuKT^@RsEXg4+N5GBpwXM zgPML)j+BAMHCZ85iHI5ssF6@8tT%WXjVaGJ(iCn}Tcd5ESbHoHZI8r49g$Et8tDkO zMq4_xfYxq2RK8xM@=va{`dBhGFfukekQyEt90zV&Js)YreKjN@1p_bwV=#)x6bxg3 z5XOO@DX;M}YXBJIJ%AxnyhoVJb-2e^l#QN-Bjz}U$LQ^jk*LJ=nxQfyWo;Z&x!@tV zaoNvhg-6(%Y?zP<+IF__){MV_#8^od*5Y{M`?CN^iqMon9|AbZmxBiJOKyLjLTY%j9=hi=wOAvScCw2km)<3uYIlBab zQigDYum7QxMqD=k4?;QBSVl(@Wo4hUTt&|E4`S2gHIez8Hq*P=V7h>^Bu{MrCP%j? zK&FMiNz}7Qp{{`}Ctv5D^J^zrMQ$$1`PTpci2%Pp2xZ_+{#m36=>I|V_n{g{#fBeF H*m?I0g#ary From 8a597fa60336d466a8661f380ca07236584f73cb Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 8 Jan 2011 17:08:41 -0800 Subject: [PATCH 016/847] Ignore a png file that's a documentation build artifact. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index bcc5997c2..4ce94ab5f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ bin/ +docs/Market Icon.png gen/ libs/ obj/ From 6a51e27ca4e57f4e7305d3bd51afa5a0a36ca09e Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sun, 9 Jan 2011 10:31:17 -0800 Subject: [PATCH 017/847] Add '|' and arrow keys to "Special Chars" Add 'Camera' button as a possible Control key Make 'Volume Down' the default control key. Change-Id: Ie9d3984efe750c8bf71ea96e6327ba29cd20f233 --- AndroidManifest.xml | 2 +- res/values/arrays.xml | 2 + res/values/styles.xml | 4 +- src/jackpal/androidterm/Term.java | 94 ++++++++++++++++++++----------- 4 files changed, 67 insertions(+), 35 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index f90187982..d2849836e 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,6 +1,6 @@ + android:versionName="1.0.21" android:versionCode="22"> diff --git a/res/values/arrays.xml b/res/values/arrays.xml index 82a31bfa4..f9b860f68 100644 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -104,6 +104,7 @@ Right Alt key Vol Up key Vol Down key + Camera key @@ -114,6 +115,7 @@ 3 4 5 + 6 diff --git a/res/values/styles.xml b/res/values/styles.xml index 0e59f4a76..213c38d99 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -4,9 +4,9 @@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - + http://www.apache.org/licenses/LICENSE-2.0 - + Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index 36e970cd7..27bb52703 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -134,7 +134,7 @@ public class Term extends Activity { private int mCursorBlink = 0; private int mFontSize = 9; private int mColorId = 2; - private int mControlKeyId = 0; + private int mControlKeyId = 5; // Default to Volume Down private int mUseCookedIME = 0; private static final String STATUSBAR_KEY = "statusbar"; @@ -163,10 +163,11 @@ public class Term extends Activity { KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_ALT_RIGHT, KeyEvent.KEYCODE_VOLUME_UP, - KeyEvent.KEYCODE_VOLUME_DOWN + KeyEvent.KEYCODE_VOLUME_DOWN, + KeyEvent.KEYCODE_CAMERA }; private static final String[] CONTROL_KEY_NAME = { - "Ball", "@", "Left-Alt", "Right-Alt", "Vol-Up", "Vol-Dn" + "Ball", "@", "Left-Alt", "Right-Alt", "Vol-Up", "Vol-Dn", "Camera" }; private int mControlKeyCode; @@ -563,14 +564,19 @@ private void doDocumentKeys() { String controlKey = CONTROL_KEY_NAME[mControlKeyId]; new AlertDialog.Builder(this). setTitle("Press " + controlKey + " and Key"). - setMessage(controlKey + " Space ==> Control-@ (NUL)\n" - + controlKey + " A..Z ==> Control-A..Z\n" - + controlKey + " 1 ==> Control-[ (ESC)\n" - + controlKey + " 5 ==> Control-_\n" - + controlKey + " . ==> Control-\\\n" - + controlKey + " 0 ==> Control-]\n" - + controlKey + " 6 ==> Control-^"). - show(); + setMessage(controlKey + " Space : Control-@ (NUL)\n" + + controlKey + " A..Z : Control-A..Z\n" + + controlKey + " 1 : Control-[ (ESC)\n" + + controlKey + " 2 : Control-_\n" + + controlKey + " 3 : Control-^\n" + + controlKey + " 4 : Control-]\n" + + controlKey + " 5 : | (pipe)\n" + + controlKey + " 6 : Left arrow\n" + + controlKey + " 7 : Down arrow\n" + + controlKey + " 8 : Up arrow\n" + + controlKey + " 9 : Right arrow\n" + + controlKey + " . : Control-\\" + ).show(); } private void doToggleSoftKeyboard() { @@ -2873,8 +2879,10 @@ private void sendText(CharSequence text) { } private void mapAndSend(int c) throws IOException { - mTermOut.write( - mKeyListener.mapControlChar(c)); + int result = mKeyListener.mapControlChar(c); + if (result < TermKeyListener.KEYCODE_OFFSET) { + mTermOut.write(result); + } } public boolean beginBatchEdit() { @@ -4187,6 +4195,8 @@ public boolean isActive() { private boolean mCapsLock; + static public final int KEYCODE_OFFSET = 1000; + /** * Construct a term key listener. * @@ -4215,12 +4225,22 @@ public int mapControlChar(int ch) { result = 27; } else if ((result == '\\') || (result == '.')) { result = 28; - } else if ((result == ']') || (result == '0')) { + } else if ((result == ']') || (result == '4')) { result = 29; - } else if ((result == '^') || (result == '6')) { + } else if ((result == '^') || (result == '3')) { result = 30; // control-^ - } else if ((result == '_') || (result == '5')) { + } else if ((result == '_') || (result == '2')) { result = 31; + } else if ((result == '5')) { + result = '|'; + } else if ((result == '6')) { + result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_LEFT; + } else if ((result == '7')) { + result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_DOWN; + } else if ((result == '8')) { + result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_UP; + } else if ((result == '9')) { + result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_RIGHT; } } @@ -4229,6 +4249,7 @@ public int mapControlChar(int ch) { mCapKey.adjustAfterKeypress(); mControlKey.adjustAfterKeypress(); } + return result; } @@ -4239,21 +4260,8 @@ public int mapControlChar(int ch) { * */ public void keyDown(int keyCode, KeyEvent event, OutputStream out, boolean appMode) throws IOException { - if (keyCode >= 0 && keyCode < mKeyCodes.length) { - String code = null; - if (appMode) { - code = mAppKeyCodes[keyCode]; - } - if (code == null) { - code = mKeyCodes[keyCode]; - } - if (code != null) { - int length = code.length(); - for (int i = 0; i < length; i++) { - out.write(code.charAt(i)); - } - return; - } + if (handleKeyCode(keyCode, out, appMode)) { + return; } int result = -1; switch (keyCode) { @@ -4288,11 +4296,33 @@ public void keyDown(int keyCode, KeyEvent event, OutputStream out, boolean appMo result = mapControlChar(result); - if (result >= 0) { + if (result >= KEYCODE_OFFSET) { + handleKeyCode(result - KEYCODE_OFFSET, out, appMode); + } else if (result >= 0) { out.write(result); } } + private boolean handleKeyCode(int keyCode, OutputStream out, boolean appMode) throws IOException { + if (keyCode >= 0 && keyCode < mKeyCodes.length) { + String code = null; + if (appMode) { + code = mAppKeyCodes[keyCode]; + } + if (code == null) { + code = mKeyCodes[keyCode]; + } + if (code != null) { + int length = code.length(); + for (int i = 0; i < length; i++) { + out.write(code.charAt(i)); + } + return true; + } + } + return false; + } + /** * Handle a keyUp event. * From b848419d306c8ba8ffb6c6cbb9c632ccfa79cd26 Mon Sep 17 00:00:00 2001 From: Steve Kondik Date: Sun, 16 Jan 2011 05:44:59 -0500 Subject: [PATCH 018/847] Add missing InputConnection methods needed on 2.3 Change-Id: I1d6b275b6e3c2a482f732d080a39eb75fec76218 --- src/jackpal/androidterm/Term.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index 27bb52703..2c7cf0f73 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -3073,6 +3073,14 @@ public boolean setSelection(int arg0, int arg1) { } return true; } + + public boolean setComposingRegion(int start, int end) { + return true; + } + + public CharSequence getSelectedText(int flags) { + return null; + } }; } From e6a56f42f68d1e567edf7f1515c55ab6d737f79a Mon Sep 17 00:00:00 2001 From: thiasB Date: Tue, 18 Jan 2011 11:29:18 +0100 Subject: [PATCH 019/847] German updated --- res/values-de/arrays.xml | 1 + res/values-de/strings.xml | 1 + 2 files changed, 2 insertions(+) diff --git a/res/values-de/arrays.xml b/res/values-de/arrays.xml index 1bd3287fd..d0eb2e9c8 100644 --- a/res/values-de/arrays.xml +++ b/res/values-de/arrays.xml @@ -61,6 +61,7 @@ Rechte Alt-Taste Lauter Leiser + Kamera-Taste diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml index 9451a3d01..563218c3c 100644 --- a/res/values-de/strings.xml +++ b/res/values-de/strings.xml @@ -23,6 +23,7 @@ Tastatur an/aus Text bearbeiten + Text selektieren Alles kopieren Einfügen From 66a73d6567e777da1c53a3b83ef6e21632c9981a Mon Sep 17 00:00:00 2001 From: flr Date: Sun, 23 Jan 2011 07:45:37 +0100 Subject: [PATCH 020/847] Added polish translation Change-Id: Ie55e89c82355f75162d83d324230ef69296959ed --- res/values-pl/arrays.xml | 72 +++++++++++++++++++++++++++++++++++++ res/values-pl/strings.xml | 74 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 146 insertions(+) create mode 100644 res/values-pl/arrays.xml create mode 100644 res/values-pl/strings.xml diff --git a/res/values-pl/arrays.xml b/res/values-pl/arrays.xml new file mode 100644 index 000000000..2eb1f3b3e --- /dev/null +++ b/res/values-pl/arrays.xml @@ -0,0 +1,72 @@ + + + + + + Pokaż pasek stanu + Ukryj pasek stanu + + + + Niemigający kursor + Migający kursor + + + + Prostokąt + Podkreślenie + Poziomy pasek + + + + 4 x 8 pixeli + 6 pt + 7 pt + 8 pt + 9 pt + 10 pt + 12 pt + 14 pt + 16 pt + 20 pt + + + + Czarny tekst na białym tle + Biały tekst na czarnym tle + Biały tekst na niebieskim tle + Zielony tekst na czarnym tle + Bursztynowy tekst na czarnym tle + Czerwony tekst na czarnym tle + + + + Kulka/tackball + Przycisk \@ + Lewy Alt + Prawy Alt + Przycisk zwiększania głośności + Przycisk zmniejszania głośności + Przycisk aparatu + + + + oparta o litery + oparta o słowa + + + diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml new file mode 100644 index 000000000..e6b72fc84 --- /dev/null +++ b/res/values-pl/strings.xml @@ -0,0 +1,74 @@ + + + + Terminal Emulator + Ustawienia + Reset + Wyślij emailem + Przyciski specjalne + Pokaż klawiaturę + + Edytuj tekst + Wybierz tekst + Kopiuj wszystko + Wklej + + + Ekran + + Pasek stanu + Pokaż/ukryj pasek stanu. + Pasek stanu + + Styl kursora + Wybierz styl kursora. + Styl kursora + + Miganie kursora + Wybierz miganie kursora. + Miganie kursora + + Tekst + + Rozmiar czcionki + Wybierz wielkość liter. + Rozmiar czcionki + + Kolory + Wybierz kolor tekstu. + Kolor tekstu + + Klawiatura + + Przycisk control + Wybierz przycisk control. + Przycisk control + + Metoda wprowadzania + Wybierz metodę wprowadzania. + Metoda wprowadzania + + Powłoka + Linia poleceń + Określ linię powłokę. + Powłoka + + Komenda początkowa + Wysyłana do powłoki podczas startu. + Komenda początkowa + + From 94ecce1319e25327b2fd09fefd48d0e047889744 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Tue, 8 Feb 2011 10:18:35 -0800 Subject: [PATCH 021/847] Add Android.mk file To help people trying to incorporate AndroidTerm in their own custom ROMs. (This hasn't been tested, but it might work.) --- Android.mk | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 Android.mk diff --git a/Android.mk b/Android.mk new file mode 100644 index 000000000..211629580 --- /dev/null +++ b/Android.mk @@ -0,0 +1,38 @@ +# +# Copyright (C) 2011 Jack Palevich +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# This makefile shows how to build a shared library and an activity that +# bundles the shared library and calls it using JNI. + +TOP_LOCAL_PATH:= $(call my-dir) + +# Build activity + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-subdir-java-files) + +LOCAL_PACKAGE_NAME := AndroidTerm + +LOCAL_JNI_SHARED_LIBRARIES := libandroidterm + +include $(BUILD_PACKAGE) + +# ============================================================ + +# Also build all of the sub-targets under this one: the shared library. +include $(call all-makefiles-under,$(LOCAL_PATH)) \ No newline at end of file From f0e8f969ba98b13f2a5172ac139e15636c0da03c Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Wed, 9 Feb 2011 09:00:41 -0800 Subject: [PATCH 022/847] Mark this module optional. --- Android.mk | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Android.mk b/Android.mk index 211629580..378b7921a 100644 --- a/Android.mk +++ b/Android.mk @@ -28,6 +28,8 @@ LOCAL_SRC_FILES := $(call all-subdir-java-files) LOCAL_PACKAGE_NAME := AndroidTerm +LOCAL_MODULE_TAGS := optional + LOCAL_JNI_SHARED_LIBRARIES := libandroidterm include $(BUILD_PACKAGE) From 61c5cc488bd74d22fd847fb11cc320ae95402d86 Mon Sep 17 00:00:00 2001 From: ladios Date: Tue, 15 Feb 2011 03:45:09 +0800 Subject: [PATCH 023/847] Traditional Chinese: Translation added. Change-Id: I587df7f286798c2455749c7d8605141b151e6e84 --- res/values-zh-rTW/arrays.xml | 92 +++++++++++++++++++++++++++++++++++ res/values-zh-rTW/strings.xml | 84 ++++++++++++++++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 res/values-zh-rTW/arrays.xml create mode 100644 res/values-zh-rTW/strings.xml diff --git a/res/values-zh-rTW/arrays.xml b/res/values-zh-rTW/arrays.xml new file mode 100644 index 000000000..fc4874481 --- /dev/null +++ b/res/values-zh-rTW/arrays.xml @@ -0,0 +1,92 @@ + + + + + + 顯示狀態列 + 隱藏狀態列 + + + + + + + 非閃爍式浮標 + 閃爍式浮標 + + + + + + + 方形 + 底線 + 直線 + + + + + + + 4 x 8 像素 + 6 點 + 7 點 + 8 點 + 9 點 + 10 點 + 12 點 + 14 點 + 16 點 + 20 點 + + + + + + + 白底黑字 + 黑底白字 + 藍底白字 + 黑底綠字 + 黑底黄字 + 黑底紅字 + + + + + + + 軌跡球 + \@ 鍵 + 左 Alt 鍵 + 右 Alt 鍵 + 提升音量鍵 + 降低音量鍵 + 相機快門鍵 + + + + + + + 字本 + 詞本 + + + + + diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml new file mode 100644 index 000000000..8a5912cd4 --- /dev/null +++ b/res/values-zh-rTW/strings.xml @@ -0,0 +1,84 @@ + + + + 模擬終端 + 設定 + 結束 + 以電郵傳送 + 特別按鍵 + 顯示/隱藏鍵盤 + + 編輯文字 + 選擇文字 + 全部複製 + 貼上 + + + 螢幕 + + 狀態列 + 顯示/隱藏狀態列。 + 狀態列 + + 浮標風格 + 選擇浮標風格。 + 浮標風格 + + 浮標閃爍 + 選擇浮標是否閃爍。 + 浮標閃爍 + + 文字 + + 字型大小 + 選擇字元高度。 + 字型大小 + + 色彩 + 選擇文字色彩。 + 文字色彩 + + 鍵盤 + + Ctrl 鍵 + 選擇 Ctrl 鍵。 + Ctrl 鍵 + + 輸入方式 + 選擇軟體鍵盤的輸入方式。 + 輸入方式 + + 殼層 + 命令列 + 指定殼層的命令列。 + 殼層 + + 初始命令 + 在啟動時傳送至殼層。 + 初始命令 + + + + + + + + + + + + From 21c555bdbb45feaf967f2db6986a928f0d6efb6c Mon Sep 17 00:00:00 2001 From: Untouchab1e Date: Thu, 17 Feb 2011 07:48:24 +0100 Subject: [PATCH 024/847] =?UTF-8?q?Added=20Norwegian=20(Bokm=C3=A5l)=20tra?= =?UTF-8?q?nslation=20of=20AndroidTerm?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change-Id: I7c75daba614c95c5c8c520840d91ae6ffb4a1d9a --- res/values-nb/arrays.xml | 71 +++++++++++++++++++++++++++++++++++++ res/values-nb/strings.xml | 74 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100755 res/values-nb/arrays.xml create mode 100755 res/values-nb/strings.xml diff --git a/res/values-nb/arrays.xml b/res/values-nb/arrays.xml new file mode 100755 index 000000000..7e85d5f3a --- /dev/null +++ b/res/values-nb/arrays.xml @@ -0,0 +1,71 @@ + + + + + + Vis statuslinje + Skjul statuslinje + + + + Ikke-blinkende peker + Blinkende peker + + + + Rektangulær + Understreket + Vertikal linje + + + + 4x8 Piksler + 6 Punkter + 7 Punkter + 8 Punkter + 9 Punkter + 10 Punkter + 12 Punkter + 14 Punkter + 16 Punkter + 20 Punkter + + + + Sort tekst på hvit + Hvit tekst på sort + Hvit tekst på blå + Grønn tekst på sort + Orange tekst på sort + Rød tekst på sort + + + + Trackball + \@-tast + Venstre Alt-tast + Høyre Alt-tast + Vol opp + Vol ned + Kamera-tast + + + + Tegnbasert + Ordbasert + + diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml new file mode 100755 index 000000000..d81ee0716 --- /dev/null +++ b/res/values-nb/strings.xml @@ -0,0 +1,74 @@ + + + + Terminal Emulator + Innstillinger + Tilbakestill terminal + Send epost til + Spesielle tegn + Veksle virtuelt tastatur + + Rediger tekst + Marker tekst + Kopier alt + Lim inn + + + Skjerm + + Statuslinje + Vis/Skjul statuslinje + Statuslinje + + Pekertype + Velg pekertype. + Pekertype + + Blinkende peker + Velg blinkende peker + Blinkende peker + + Tekst + + Skriftstørrelse + Velg tegnhøyde i ant punkter. + Skriftstørrelse + + Farger + Velg tekstfarge. + Tekstfarge + + Tastatur + + Ctrl-tast + Velg Ctrl-tast + Ctrl-tast + + Input-type + Velg input-type for virtuelt tastatur. + Input-type + + Shell + Kommandolinje + Velg shell kommandolinje. + Shell + + Innledende kommando + Sendes til shellet når det starter. + Innledende kommando + + From 7314da97f676b019505be29019c6924f865b5cf6 Mon Sep 17 00:00:00 2001 From: Ricardo Pinho Date: Sat, 19 Feb 2011 15:32:18 +0000 Subject: [PATCH 025/847] Added Portuguese Translation Change-Id: Ia797f8491436b3fee2ce8f96a179d3aa5a8a0362 --- res/values-pt-rPT/arrays.xml | 72 ++++++++++++++++++++++++++++++++++ res/values-pt-rPT/strings.xml | 74 +++++++++++++++++++++++++++++++++++ 2 files changed, 146 insertions(+) create mode 100755 res/values-pt-rPT/arrays.xml create mode 100755 res/values-pt-rPT/strings.xml diff --git a/res/values-pt-rPT/arrays.xml b/res/values-pt-rPT/arrays.xml new file mode 100755 index 000000000..8042291bf --- /dev/null +++ b/res/values-pt-rPT/arrays.xml @@ -0,0 +1,72 @@ + + + + + + Mostrar barra de notificações + Esconder barra de notificações + + + + Cursor fixo + Cursor a piscar + + + + Rectangular + Sublinhado + Barra vertical + + + + 4 x 8 píxeis + 6 pt + 7 pt + 8 pt + 9 pt + 10 pt + 12 pt + 14 pt + 16 pt + 20 pt + + + + Texto preto em fundo branco + Texto branco em fundo preto + Texto branco em fundo azul + Texto verde em fundo preto + Texto âmbar em fundo preto + Texto vermelho em fundo preto + + + + Jog ball + \@ key + Left Alt key + Right Alt key + Vol Up key + Vol Down key + Camera key + + + + Baseado em caracteres + Baseado em palavras + + + diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml new file mode 100755 index 000000000..aa4ba544d --- /dev/null +++ b/res/values-pt-rPT/strings.xml @@ -0,0 +1,74 @@ + + + + Terminal Emulator + Preferências + Reset terminal + Email para + Teclas especiais + Abrir teclado + + Editar texto + Seleccionar texto + Copiar tudo + Colar + + + Ecrã + + Barra de notificações + Mostrar/esconder barra de notificações + Barra de notificações + + Estilo do cursor + Escolha o estilo do cursor + Estilo do cursor + + Piscar do cursor + Escolher piscar do cursor + Piscar do cursor + + Texto + + Tamanho do texto + Escolher a altura dos caracteres em pontos + Tamanho do texto + + Côres + Escolher côr do texto + Côr do texto + + Teclado + + Tecla control + Escolher tecla control + Tecla control + + Método de entrada + Escolher método de entrada + Método de entrada + + Shell + Linha de comandos + Especificar a linha de comando do shell + Shell + + Comando inicial + Enviar para o shell no arranque + Comando inicial + + From 4a0ea611ec561c6e7de9263dedf6aac2a33b01d0 Mon Sep 17 00:00:00 2001 From: Gustavo Date: Fri, 25 Feb 2011 18:12:33 -0800 Subject: [PATCH 026/847] Added missing PT-BR translations Change-Id: Ibcbe0eec530a75f515442f26b78ffd595f88e9c8 --- res/values-pt/arrays.xml | 72 ++++++++++++++++++++++++++++++++++++++ res/values-pt/strings.xml | 73 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100755 res/values-pt/arrays.xml create mode 100755 res/values-pt/strings.xml diff --git a/res/values-pt/arrays.xml b/res/values-pt/arrays.xml new file mode 100755 index 000000000..78e443709 --- /dev/null +++ b/res/values-pt/arrays.xml @@ -0,0 +1,72 @@ + + + + + + Mostrar barra de notificações + Esconder barra de notificações + + + + Cursor fixo + Piscar cursor + + + + Retangular + Sublinhado + Barra vertical + + + + 4 x 8 píxeis + 6 pt + 7 pt + 8 pt + 9 pt + 10 pt + 12 pt + 14 pt + 16 pt + 20 pt + + + + Texto preto em fundo branco + Texto branco em fundo preto + Texto branco em fundo azul + Texto verde em fundo preto + Texto âmbar em fundo preto + Texto vermelho em fundo preto + + + + Jog ball + \@ key + Left Alt key + Right Alt key + Vol Up key + Vol Down key + Camera key + + + + Baseado em caracteres + Baseado em palavras + + + diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml new file mode 100755 index 000000000..bc551a787 --- /dev/null +++ b/res/values-pt/strings.xml @@ -0,0 +1,73 @@ + + + + Terminal Emulator + Preferências + Resetar terminal + Email para + Teclas especiais + Abrir teclado + + Editar texto + Selecionar texto + Copiar tudo + Colar + + + Tela + + Barra de notificações + Mostrar/esconder barra de notificações + Barra de notificações + + Estilo do cursor + Escolha o estilo do cursor + Estilo do cursor + + Piscar do cursor + Escolher piscar do cursor + Piscar do cursor + + Texto + + Tamanho do texto + Escolher a altura dos caracteres em pontos + Tamanho do texto + + Cores + Escolher cor do texto + Cor do texto + + Teclado + + Tecla control + Escolher tecla control + Tecla control + + Método de entrada + Escolher método de entrada + Método de entrada + + Shell + Linha de comandos + Especificar a linha de comando do shell + Shell + + Comando inicial + Enviar para o shell ao iniciar. + Comando inicial + From 9da22d77438ce568e409424c95036dd09d92c319 Mon Sep 17 00:00:00 2001 From: KalimochoAz Date: Fri, 11 Mar 2011 15:37:21 +0100 Subject: [PATCH 027/847] AndroidTerminal Spanish translation ( by Chuckycrx ) Patch 2: Removed values Change-Id: I43d5cd2df3bf896eb1f3dcada10555df97d05d92 --- res/values-es/strings.xml | 74 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 res/values-es/strings.xml diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml new file mode 100644 index 000000000..8070b3cdc --- /dev/null +++ b/res/values-es/strings.xml @@ -0,0 +1,74 @@ + + + + Terminal + Preferencias + Resetear terminal + Enviar Email + Teclas especiales + Activar teclado + + Editar texto + Seleccionar texto + Copiar todo + Pegar + + + Pantalla + + Barra de estado + Ver/ocultar barra de estado. + Barra de estado + + Estilo cursor + Escoja estilo de cursor. + Estilo cursor + + Parpadeo cursor + Escoja parpadeo de cursor. + Parpadeo cursor + + Texto + + Tamaño Fuente + Elija altura de los carácteres en puntos. + Tamaño Fuente + + Colores + Elija color de texto. + Color de texto + + Teclado + + Tecla Control + Elija tecla de control. + Tecla Control + + Método de entrada + Elija estodo de entrada para el teclado. + Método de entrada + + Shell + Línea de comandos + Especifique la línea de comandos de shell. + Shell + + Comando inicial + Enviar al shell cuando se inicia. + Comando inicial + + From e08a0add0be2f0827d7cd4b0c158bd6ab7c61adb Mon Sep 17 00:00:00 2001 From: Sven Dawitz Date: Thu, 7 Apr 2011 23:02:06 +0200 Subject: [PATCH 028/847] AndroidTerm: Changes for tablet tweaks Change-Id: I42fa431fa1b25d5ba6ef54f67b5ed56c36d248f4 --- src/jackpal/androidterm/Term.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index 2c7cf0f73..6d160e47e 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -400,7 +400,11 @@ private void updatePrefs() { if (mAlreadyStarted) { // Can't switch to/from fullscreen after // starting the activity. - restart(); + // + // remove restart for the sake of tablet tweaks, + // since this app else creates a soft reboot + // due to endless restarting + //restart(); } else { win.setFlags(desiredFlag, FULLSCREEN); } From 71150215a2dac8bda582be6f2e7e9ad06969d7bb Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sun, 10 Apr 2011 15:24:05 -0700 Subject: [PATCH 029/847] Add support for Android 1.5 + set minSdkVersion to 3, so we run on Android 1.5 + set targetSdkVersion to 11, so we run without compatability hacks. - otherwise the terminal emulator screen size calculation is incorrect and scrolling doesn't work on Android 2.3 devices. + add a res/drawable/app_terminal.png file, so that we get a custom launcher icon on Android 1.5 devices. + bump version name to 1.0.22. + add files to support building from command line using "ant". --- AndroidManifest.xml | 4 +- build.properties | 2 + build.xml | 84 ++++++++++++++++++++++++++++++++++ default.properties | 2 +- docs/Building.txt | 13 ++++++ local.properties | 10 ++++ proguard.cfg | 36 +++++++++++++++ res/drawable/app_terminal.png | Bin 0 -> 1409 bytes tools/br | 8 ++++ tools/rt | 4 ++ 10 files changed, 160 insertions(+), 3 deletions(-) create mode 100644 build.properties create mode 100644 build.xml create mode 100644 local.properties create mode 100644 proguard.cfg create mode 100644 res/drawable/app_terminal.png create mode 100755 tools/br create mode 100755 tools/rt diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 0a12c808c..09b0b9cd2 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,6 +1,6 @@ + android:versionName="1.0.22" android:versionCode="23"> - + diff --git a/build.properties b/build.properties new file mode 100644 index 000000000..2799f2ac4 --- /dev/null +++ b/build.properties @@ -0,0 +1,2 @@ +key.store=../../Documents/workspace/keystore/jackpal.keystore +key.alias=jackpal.keystore diff --git a/build.xml b/build.xml new file mode 100644 index 000000000..7bb49e866 --- /dev/null +++ b/build.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/default.properties b/default.properties index 9d79b12c7..510b0908b 100644 --- a/default.properties +++ b/default.properties @@ -8,4 +8,4 @@ # project structure. # Project target. -target=android-4 +target=android-11 diff --git a/docs/Building.txt b/docs/Building.txt index d2afa34a7..79833be10 100644 --- a/docs/Building.txt +++ b/docs/Building.txt @@ -66,3 +66,16 @@ Build the Java apk: This should happen automatically once you've created the Eclipse project. +Command Line Build Instructions +------------------------------- + +You can build Android Terminal Emulator from the command line, instead of from +Eclipse. This is handy because it can be scripted. + +1) Install the "ant" build system. +2) cd +3) ant clean +4) ant debug +5) Connect a device to your machine, or start the emulator +6) adb install -r bin/Term.apk +7) adb shell am start -n jackpal.androidterm/jackpal.androidterm.Term diff --git a/local.properties b/local.properties new file mode 100644 index 000000000..78ec6f944 --- /dev/null +++ b/local.properties @@ -0,0 +1,10 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must *NOT* be checked in Version Control Systems, +# as it contains information specific to your local configuration. + +# location of the SDK. This is only used by Ant +# For customization when using a Version Control System, please read the +# header note. +sdk.dir=/Users/jack/code/android-sdk-mac_x86 diff --git a/proguard.cfg b/proguard.cfg new file mode 100644 index 000000000..12dd0392c --- /dev/null +++ b/proguard.cfg @@ -0,0 +1,36 @@ +-optimizationpasses 5 +-dontusemixedcaseclassnames +-dontskipnonpubliclibraryclasses +-dontpreverify +-verbose +-optimizations !code/simplification/arithmetic,!field/*,!class/merging/* + +-keep public class * extends android.app.Activity +-keep public class * extends android.app.Application +-keep public class * extends android.app.Service +-keep public class * extends android.content.BroadcastReceiver +-keep public class * extends android.content.ContentProvider +-keep public class * extends android.app.backup.BackupAgentHelper +-keep public class * extends android.preference.Preference +-keep public class com.android.vending.licensing.ILicensingService + +-keepclasseswithmembernames class * { + native ; +} + +-keepclasseswithmembernames class * { + public (android.content.Context, android.util.AttributeSet); +} + +-keepclasseswithmembernames class * { + public (android.content.Context, android.util.AttributeSet, int); +} + +-keepclassmembers enum * { + public static **[] values(); + public static ** valueOf(java.lang.String); +} + +-keep class * implements android.os.Parcelable { + public static final android.os.Parcelable$Creator *; +} diff --git a/res/drawable/app_terminal.png b/res/drawable/app_terminal.png new file mode 100644 index 0000000000000000000000000000000000000000..1ec3b4b887732d33e6d80ce60624980bfb7e1ce3 GIT binary patch literal 1409 zcmV-{1%CR8P)aMb~g0w2Mw4uuCgB8~&T@YUiN}c#Qfa3J!hQJ#EBYfx_Hf;%VCn4arFD{ch0%v zy;tI#LpGZYvfL~JvYi0svWpcJ71Gw$7I?j0aJ$_?0XsfEhP}N#;8!F$Db;mFQEZGc zTTf3DJZfue`=Pb9 zHD7e_3E}W~et!O^NF>rYJ3G7P^ZDX4Gc&xox%o^1k|fD^aF36Ve~aKhyo(8MZf@?I z{{H>}{CI@8*=ZH9+wBzNj+&a9x0;%o?j(3Jzu(_O7zr|}TRJ5m9*@(}F*G#v-d&M> zDk~@`fZ^fccL^(ogOr#9GzA&%4QxJox`YH^)TZhA`w8P z-RQU*y$gs+04X3<@Bb$PL<|8#0qKyCmIBB{%mJ1HGNk~0w3wYq&|vbeltD`Y*-!>E znga|bMN&XUtAJ4k8Dm1d4Q5mU^q{QH0o3~<5|A+kM59sI*w}!gq9SlQowqvncXxL+ z&!__EVf1cIt|*E|0=dKC09lr`W4gaEQ%0RafWzUiR-q!1h<5IO%K?T_IM6>y$Wz@* zWL)(9x<+A_7<%WwUsSTMV`(ekIz5wUG)0x^?4GV=Y$W))<^4J>tq>5E080Uu0xShs z3a}JlDZo;Ir2tC-mI7`-KxJj6_P+qpj8?jomX_X}fR>gPjRet*mcyj8+($|0VYiV0 zt~V7M9h0Pw4Fgv@FI}<5QxP$?30MruD?`8N}`Vs2t>rLHUx3ciSH+lAr@VVcpSnO6W3nf4@ zHV^kev04oG*u5n8sXoZ#c}d_2)8ypjbHa!ob-Uf_sRH!mK;b~!LP`b)2b=o(`abpf zeDB>QzlPP0~Qb#-+!5D2VO1fcH`cvO7c@AvbCg@xoDBQBRq3Wvi)0!;wsQgp!C!eY_3 z&2VkVfO1C<>mJ1_5NC|>l;eO Date: Sun, 10 Apr 2011 15:43:28 -0700 Subject: [PATCH 030/847] Declare that we don't require a touchscreen. --- AndroidManifest.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 09b0b9cd2..63e42fb27 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,6 +1,6 @@ + android:versionName="1.0.23" android:versionCode="24"> + From c781b765707090cc585be8a3b534a084276c5f40 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sun, 10 Apr 2011 16:28:30 -0700 Subject: [PATCH 031/847] Declare SDK target 10 (Gingerbread) rather than 11 (Honeycomb) Otherwise the Options menu key doesn't appear on Honeycomb devices. --- AndroidManifest.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 63e42fb27..2ef7d2b30 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,6 +1,6 @@ + android:versionName="1.0.24" android:versionCode="25"> - + From 364e3cc54093fbe78f50568ed84f7fe1bf368e37 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sun, 17 Apr 2011 07:51:03 -0700 Subject: [PATCH 032/847] Turkish localization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Provided by Doğukan Korkmaztürk --- AndroidManifest.xml | 2 +- res/values-tr/arrays.xml | 131 ++++++++++++++++++++++++++++++++++++++ res/values-tr/strings.xml | 84 ++++++++++++++++++++++++ 3 files changed, 216 insertions(+), 1 deletion(-) create mode 100755 res/values-tr/arrays.xml create mode 100755 res/values-tr/strings.xml diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 2ef7d2b30..0d7dfdc3c 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,6 +1,6 @@ + android:versionName="1.0.25" android:versionCode="26"> + + + + + Uyarı çubuğunu göster + Uyarı çubuğunu sakla + + + + + 1 + 0 + + + + Non-blinking cursor + Blinking cursor + + + + + 0 + 1 + + + + Rectangle + Underline + Vertical bar + + + + + 0 + 1 + 2 + + + + 4 x 8 piksel + 6 pt + 7 pt + 8 pt + 9 pt + 10 pt + 12 pt + 14 pt + 16 pt + 20 pt + + + + + 0 + 6 + 7 + 8 + 9 + 10 + 12 + 14 + 16 + 20 + + + + Beyaz arkaplan üzerinde siyah metin + Siyah arkaplan üzerinde beyaz metin + Mavi arkaplan üzerinde beyaz metin + Siyah arkaplan üzerinde yeşil metin + Siyah arkaplan üzerinde turuncu metin + Siyah arkaplan üzerinde kırmızı metin + + + + + 0 + 1 + 2 + 3 + 4 + 5 + + + + Jog ball + \@ Tuşu + Sol Alt Tuşu + Sağ Alt Tuşu + Ses Arttırma Tuşu + Ses Kısma Tuşu + Kamera Tuşu + + + + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + + + + Harf tabanlı + Kelime tabanlı + + + + + 0 + 1 + + diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml new file mode 100755 index 000000000..7462d5876 --- /dev/null +++ b/res/values-tr/strings.xml @@ -0,0 +1,84 @@ + + + + Terminal Emülatörü + Tercihler + Terminali yeniden başlat + Email olarak yolla + Özel tuşlar + Ekran klavyesine geç + + Metni düzenle + Metni seç + Hepsini kopyala + Yapıştır + + + Ekran + + Uyarı çubuğu + Uyarı çubuğunu göster/gizle. + Uyarı çubuğu + + İmleç stili + İmleç stili seçin. + İmleç stili + + Cursor blink + Choose cursor blink. + Cursor blink + + Metin + + Karakter boyutu + Karakter büyüklüğü seçin. + Karakter boyutu + + Renkler + Metin rengini seçin. + Metin rengi + + Klavye + + Kontrol tuşu + Kontrol tuşunu seçin. + Kontrol tuşu + + Giriş yöntemi + Ekran klavyesi için giriş yöntemi seçin. + Giriş yöntemi + + Kabuk + Komut satırı + Kabukta kullanılacak komut satırını belirtin. + Kabuk + + Başlangıç komutu + Başlangıçta kabukta çalıştır. + Başlangıç komutu + + + 0 + 0 + 0 + 10 + 2 + 0 + 0 + /system/bin/sh - + export PATH=/data/local/bin:$PATH + From a1acc66a23f61a2ecf9f2395386e28572c217a1e Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Sat, 23 Apr 2011 10:17:13 -0700 Subject: [PATCH 033/847] Remove unused variable in native implementation of Exec.close() --- jni/termExec.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/jni/termExec.cpp b/jni/termExec.cpp index e7e11983b..4a3a3c171 100644 --- a/jni/termExec.cpp +++ b/jni/termExec.cpp @@ -224,7 +224,6 @@ static int android_os_Exec_waitFor(JNIEnv *env, jobject clazz, static void android_os_Exec_close(JNIEnv *env, jobject clazz, jobject fileDescriptor) { int fd; - struct winsize sz; fd = env->GetIntField(fileDescriptor, field_fileDescriptor_descriptor); From 36a529bff7fe780cef5023c9b26fafba032b976a Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 23 Apr 2011 10:55:56 -0700 Subject: [PATCH 034/847] Kill the started process group in onDestroy As it stands now, if the user presses the Back button without closing her shell, it will keep running unattached to any tty. Over time, this leads to a buildup of shells, each inaccessible to the user. Fix this by doing what xterm and other terminals do -- send SIGHUP to the process group of the started process when quitting. --- jni/termExec.cpp | 10 +++++++++- src/jackpal/androidterm/Exec.java | 5 +++++ src/jackpal/androidterm/Term.java | 15 ++++++++++++--- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/jni/termExec.cpp b/jni/termExec.cpp index 4a3a3c171..f4ed7b9ca 100644 --- a/jni/termExec.cpp +++ b/jni/termExec.cpp @@ -47,6 +47,7 @@ #include #include #include +#include static jclass class_fileDescriptor; static jfieldID field_fileDescriptor_descriptor; @@ -234,6 +235,11 @@ static void android_os_Exec_close(JNIEnv *env, jobject clazz, jobject fileDescri close(fd); } +static void android_os_Exec_hangupProcessGroup(JNIEnv *env, jobject clazz, + jint procId) { + kill(-procId, SIGHUP); +} + static int register_FileDescriptor(JNIEnv *env) { @@ -270,7 +276,9 @@ static JNINativeMethod method_table[] = { { "waitFor", "(I)I", (void*) android_os_Exec_waitFor}, { "close", "(Ljava/io/FileDescriptor;)V", - (void*) android_os_Exec_close} + (void*) android_os_Exec_close}, + { "hangupProcessGroup", "(I)V", + (void*) android_os_Exec_hangupProcessGroup} }; /* diff --git a/src/jackpal/androidterm/Exec.java b/src/jackpal/androidterm/Exec.java index 82d253ae8..b7e9cd2c9 100644 --- a/src/jackpal/androidterm/Exec.java +++ b/src/jackpal/androidterm/Exec.java @@ -71,5 +71,10 @@ public static native void setPtyWindowSize(FileDescriptor fd, * Close a given file descriptor. */ public static native void close(FileDescriptor fd); + + /** + * Send SIGHUP to a process group. + */ + public static native void hangupProcessGroup(int processId); } diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index 36be5ec15..9216a1071 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -118,6 +118,11 @@ public class Term extends Activity { */ private FileOutputStream mTermOut; + /** + * The process ID of the remote process. + */ + private int mProcId = 0; + /** * A key listener that tracks the modifier keys and allows the full ASCII * character set to be entered. @@ -220,6 +225,10 @@ public void onCreate(Bundle icicle) { @Override public void onDestroy() { super.onDestroy(); + if (mProcId != 0) { + Exec.hangupProcessGroup(mProcId); + mProcId = 0; + } if (mTermFd != null) { Exec.close(mTermFd); mTermFd = null; @@ -230,7 +239,7 @@ private void startListening() { int[] processId = new int[1]; createSubprocess(processId); - final int procId = processId[0]; + mProcId = processId[0]; final Handler handler = new Handler() { @Override @@ -241,8 +250,8 @@ public void handleMessage(Message msg) { Runnable watchForDeath = new Runnable() { public void run() { - Log.i(Term.LOG_TAG, "waiting for: " + procId); - int result = Exec.waitFor(procId); + Log.i(Term.LOG_TAG, "waiting for: " + mProcId); + int result = Exec.waitFor(mProcId); Log.i(Term.LOG_TAG, "Subprocess exited: " + result); handler.sendEmptyMessage(result); } From 3c45242049107733f04b26c0b6eaf90a8da72d0e Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 23 Apr 2011 11:04:59 -0700 Subject: [PATCH 035/847] Add an Fn key for easier access to extra keys Instead of overloading the Ctrl key to provide arrow keys and send some control characters, add a new "Fn" key (defaulting to Volume Up) to provide these functions. By splitting Ctrl and Fn, we can reach these functions via the more easily-accessible letters, and also provide access to more useful keys (such as PageUp/Down and Tab). The mapping is this: Fn-W/A/S/D: Up/Left/Down/Right Fn-P/N: PageUp/PageDown Fn-T: Tab Fn-L: | Fn-U: _ Fn-E: Ctrl-[ (Esc) Fn-.: Ctrl-\ Fn-2: Ctrl-_ Fn-3: Ctrl-^ Fn-4: Ctrl-] --- res/values/arrays.xml | 21 +++++ res/values/strings.xml | 7 +- res/xml/preferences.xml | 9 +++ src/jackpal/androidterm/Term.java | 124 ++++++++++++++++++++++++------ 4 files changed, 137 insertions(+), 24 deletions(-) diff --git a/res/values/arrays.xml b/res/values/arrays.xml index f9b860f68..ab272069f 100644 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -118,6 +118,27 @@ 6 + + Jog ball + \@ key + Left Alt key + Right Alt key + Vol Up key + Vol Down key + Camera key + + + + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + + Character-based Word-based diff --git a/res/values/strings.xml b/res/values/strings.xml index a39e26f79..1c7d56dcb 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -58,6 +58,10 @@ Choose control key. Control key + Fn key + Choose Fn key. + Fn key + Input method Choose input method for soft keyboard. Input method @@ -77,7 +81,8 @@ 0 10 2 - 0 + 5 + 4 0 /system/bin/sh - export PATH=/data/local/bin:$PATH diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml index df50b011a..474ef2520 100644 --- a/res/xml/preferences.xml +++ b/res/xml/preferences.xml @@ -87,6 +87,15 @@ android:entryValues="@array/entryvalues_controlkey_preference" android:dialogTitle="@string/dialog_title_controlkey_preference" /> + + = 'a' && result <= 'z') { result = (char) (result - 'a' + '\001'); + } else if (result >= '0' && result <= '9') { + result = (char) (result - '0' + '\001'); } else if (result == ' ') { result = 0; - } else if ((result == '[') || (result == '1')) { + } else if (result == '[') { result = 27; - } else if ((result == '\\') || (result == '.')) { + } else if (result == '\\') { result = 28; - } else if ((result == ']') || (result == '4')) { + } else if (result == ']') { result = 29; - } else if ((result == '^') || (result == '3')) { + } else if (result == '^') { result = 30; // control-^ - } else if ((result == '_') || (result == '2')) { + } else if (result == '_') { result = 31; - } else if ((result == '5')) { - result = '|'; - } else if ((result == '6')) { + } + } else if (mFnKey.isActive()) { + if (result == 'w') { + result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_UP; + } else if (result == 'a') { result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_LEFT; - } else if ((result == '7')) { + } else if (result == 's') { result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_DOWN; - } else if ((result == '8')) { - result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_UP; - } else if ((result == '9')) { + } else if (result == 'd') { result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_RIGHT; + } else if (result == 'p') { + result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_PAGE_UP; + } else if (result == 'n') { + result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_PAGE_DOWN; + } else if (result == 't') { + result = KEYCODE_OFFSET + KeyEvent.KEYCODE_TAB; + } else if (result == 'l') { + result = '|'; + } else if (result == 'u') { + result = '_'; + } else if (result == 'e') { + result = 27; // ^[ (Esc) + } else if (result == '.') { + result = 28; // ^\ + } else if (result == '4') { + result = 29; // ^] + } else if (result == '3') { + result = 30; // control-^ + } else if (result == '2') { + result = 31; // ^_ } } @@ -4257,6 +4334,7 @@ public int mapControlChar(int ch) { mAltKey.adjustAfterKeypress(); mCapKey.adjustAfterKeypress(); mControlKey.adjustAfterKeypress(); + mFnKey.adjustAfterKeypress(); } return result; @@ -4312,7 +4390,7 @@ public void keyDown(int keyCode, KeyEvent event, OutputStream out, boolean appMo } } - private boolean handleKeyCode(int keyCode, OutputStream out, boolean appMode) throws IOException { + public boolean handleKeyCode(int keyCode, OutputStream out, boolean appMode) throws IOException { if (keyCode >= 0 && keyCode < mKeyCodes.length) { String code = null; if (appMode) { From 30f28f9547c0f30d3a94df2f845c5f943e9e201b Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 23 Apr 2011 11:13:25 -0700 Subject: [PATCH 036/847] Add a service to prevent Android from killing our process By default, an Android process is liable to be killed whenever it's not in the foreground. When this happens to us, it leaves the user unable to resume her terminal session, which is still running. By starting a service in our process (even one which, like this one, does nothing at all) and marking it as a foreground service, we prevent the system from killing our process except in out-of-memory situations. --- AndroidManifest.xml | 1 + res/values/strings.xml | 2 + .../androidterm/ServiceForegroundCompat.java | 106 ++++++++++++++++++ src/jackpal/androidterm/Term.java | 13 +++ src/jackpal/androidterm/TermService.java | 71 ++++++++++++ 5 files changed, 193 insertions(+) create mode 100644 src/jackpal/androidterm/ServiceForegroundCompat.java create mode 100644 src/jackpal/androidterm/TermService.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 0d7dfdc3c..8bb9fd2e0 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -16,6 +16,7 @@ + diff --git a/res/values/strings.xml b/res/values/strings.xml index 1c7d56dcb..47b9b2fc1 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -27,6 +27,8 @@ Copy all Paste + Terminal session is running + Screen diff --git a/src/jackpal/androidterm/ServiceForegroundCompat.java b/src/jackpal/androidterm/ServiceForegroundCompat.java new file mode 100644 index 000000000..984bcddeb --- /dev/null +++ b/src/jackpal/androidterm/ServiceForegroundCompat.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2011 Steven Luo + * Copyright (C) 2011 Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jackpal.androidterm; + +import java.lang.reflect.Method; +import java.lang.reflect.InvocationTargetException; +import android.app.Service; +import android.util.Log; +import android.app.Notification; +import android.app.NotificationManager; + +/* Provide startForeground() and stopForeground() compatibility, using the + current interfaces where available and the deprecated setForeground() + interface where necessary + The idea for the implementation comes from an example in the documentation of + android.app.Service */ +public class ServiceForegroundCompat { + private static Class[] mSetForegroundSig = new Class[] { + boolean.class }; + private static Class[] mStartForegroundSig = new Class[] { + int.class, Notification.class }; + private static Class[] mStopForegroundSig = new Class[] { + boolean.class }; + + private Service service; + private NotificationManager mNM; + private Method mSetForeground; + private Method mStartForeground; + private Method mStopForeground; + private int notifyId; + + private void invokeMethod(Object receiver, Method method, Object... args) { + try { + method.invoke(receiver, args); + } catch (IllegalAccessException e) { + // Shouldn't happen, but we have to catch this + Log.w("ServiceCompat", "Unable to invoke method", e); + } catch (InvocationTargetException e) { + /* The methods we call don't throw exceptions -- in general, + we should throw e.getCause() */ + Log.w("ServiceCompat", "Method threw exception", e.getCause()); + } + } + + public void startForeground(int id, Notification notification) { + if (mStartForeground != null) { + invokeMethod(service, mStartForeground, id, notification); + return; + } + + invokeMethod(service, mSetForeground, Boolean.TRUE); + mNM.notify(id, notification); + notifyId = id; + } + + public void stopForeground(boolean removeNotify) { + if (mStopForeground != null) { + invokeMethod(service, mStopForeground, removeNotify); + return; + } + + if (removeNotify) { + mNM.cancel(notifyId); + } + invokeMethod(service, mSetForeground, Boolean.FALSE); + } + + public ServiceForegroundCompat(Service service) { + this.service = service; + mNM = (NotificationManager)service.getSystemService(service.NOTIFICATION_SERVICE); + + Class clazz = service.getClass(); + + try { + mStartForeground = clazz.getMethod("startForeground", mStartForegroundSig); + mStopForeground = clazz.getMethod("stopForeground", mStopForegroundSig); + } catch (NoSuchMethodException e) { + mStartForeground = mStopForeground = null; + } + + try { + mSetForeground = clazz.getMethod("setForeground", mSetForegroundSig); + } catch (NoSuchMethodException e) { + mSetForeground = null; + } + + if (mStartForeground == null && mSetForeground == null) { + throw new IllegalStateException("Neither startForeground() or setForeground() present!"); + } + } +} diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index f5ddd7bbb..25d31f624 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -68,6 +68,12 @@ import android.view.inputmethod.InputMethodManager; import android.widget.TextView; +import android.content.ComponentName; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Binder; +import android.os.IBinder; + /** * A terminal emulator activity. */ @@ -207,6 +213,9 @@ public class Term extends Activity { private boolean mAlreadyStarted = false; + public TermService mTermService; + private Intent TSIntent; + @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); @@ -214,6 +223,9 @@ public void onCreate(Bundle icicle) { mPrefs = PreferenceManager.getDefaultSharedPreferences(this); readPrefs(); + TSIntent = new Intent(this, TermService.class); + startService(TSIntent); + setContentView(R.layout.term_activity); mEmulatorView = (EmulatorView) findViewById(EMULATOR_VIEW); @@ -248,6 +260,7 @@ public void onDestroy() { Exec.close(mTermFd); mTermFd = null; } + stopService(TSIntent); } private void startListening() { diff --git a/src/jackpal/androidterm/TermService.java b/src/jackpal/androidterm/TermService.java new file mode 100644 index 000000000..00b53e860 --- /dev/null +++ b/src/jackpal/androidterm/TermService.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jackpal.androidterm; + +import android.app.Service; +import android.os.IBinder; +import android.content.Intent; +import android.util.Log; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; + +public class TermService extends Service +{ + /* Parallels the value of START_STICKY on API Level >= 5 */ + private static final int COMPAT_START_STICKY = 1; + + private static final int RUNNING_NOTIFICATION = 1; + private ServiceForegroundCompat compat; + + @Override + public void onStart(Intent intent, int flags) { + } + + /* This should be @Override if building with API Level >=5 */ + public int onStartCommand(Intent intent, int flags, int startId) { + return COMPAT_START_STICKY; + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public void onCreate() { + compat = new ServiceForegroundCompat(this); + + /* Put the service in the foreground. */ + Notification notification = new Notification(R.drawable.app_terminal, getText(R.string.service_notify_text), System.currentTimeMillis()); + notification.flags |= Notification.FLAG_ONGOING_EVENT; + Intent notifyIntent = new Intent(this, Term.class); + notifyIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notifyIntent, 0); + notification.setLatestEventInfo(this, getText(R.string.application_terminal), getText(R.string.service_notify_text), pendingIntent); + compat.startForeground(RUNNING_NOTIFICATION, notification); + + Log.d(Term.LOG_TAG, "TermService started"); + return; + } + + @Override + public void onDestroy() { + compat.stopForeground(true); + return; + } +} From 8659494a2705d428df91284e84f2baab53a35da0 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 23 Apr 2011 11:17:29 -0700 Subject: [PATCH 037/847] Add menu options to take WakeLock and WifiLock It can be very useful to prevent the device from sleeping or from turning off the wifi radio -- but in a general terminal application it's difficult to guess when this is wanted. Consider the following: (1) Nothing is happening. We don't want to hold either a WakeLock or WifiLock. (2) The user is logged into a remote system via SSH on wifi. We don't need to hold a WakeLock, but should take a WifiLock to prevent the SSH session from being disconnected. (3) The user is doing some long-running computation (locally on the device) in the shell. We should take a WakeLock, but not a WifiLock. (4) The user is doing apt-get dist-upgrade in her Debian chroot on the device. We should hold both a WakeLock and a WifiLock. Therefore, assume the user knows best, and add menu options to take/drop a WakeLock and a WifiLock. Note that this requires adding WAKE_LOCK to the set of permissions we use. --- AndroidManifest.xml | 1 + res/menu/main.xml | 4 +++ res/values/strings.xml | 5 +++ src/jackpal/androidterm/Term.java | 54 +++++++++++++++++++++++++++++++ 4 files changed, 64 insertions(+) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 8bb9fd2e0..f0acb0218 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -3,6 +3,7 @@ android:versionName="1.0.25" android:versionCode="26"> + + + android:title="@string/enable_wakelock" /> + + android:title="@string/enable_wifilock" /> diff --git a/res/values/strings.xml b/res/values/strings.xml index 47b9b2fc1..fde9c597d 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -22,6 +22,11 @@ Special keys Toggle soft keyboard + Take WakeLock + Drop WakeLock + Take WifiLock + Drop WifiLock + Edit text Select text Copy all diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index 25d31f624..9cfc17b98 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -74,6 +74,9 @@ import android.os.Binder; import android.os.IBinder; +import android.net.wifi.WifiManager; +import android.os.PowerManager; + /** * A terminal emulator activity. */ @@ -216,6 +219,9 @@ public class Term extends Activity { public TermService mTermService; private Intent TSIntent; + private PowerManager.WakeLock mWakeLock; + private WifiManager.WifiLock mWifiLock; + @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); @@ -245,6 +251,11 @@ public void onCreate(Bundle icicle) { registerForContextMenu(mEmulatorView); + PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE); + mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, Term.LOG_TAG); + WifiManager wm = (WifiManager)getSystemService(Context.WIFI_SERVICE); + mWifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL, Term.LOG_TAG); + updatePrefs(); mAlreadyStarted = true; } @@ -260,6 +271,12 @@ public void onDestroy() { Exec.close(mTermFd); mTermFd = null; } + if (mWakeLock.isHeld()) { + mWakeLock.release(); + } + if (mWifiLock.isHeld()) { + mWifiLock.release(); + } stopService(TSIntent); } @@ -512,10 +529,31 @@ public boolean onOptionsItemSelected(MenuItem item) { doDocumentKeys(); } else if (id == R.id.menu_toggle_soft_keyboard) { doToggleSoftKeyboard(); + } else if (id == R.id.menu_toggle_wakelock) { + doToggleWakeLock(); + } else if (id == R.id.menu_toggle_wifilock) { + doToggleWifiLock(); } return super.onOptionsItemSelected(item); } + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + MenuItem wakeLockItem = menu.findItem(R.id.menu_toggle_wakelock); + MenuItem wifiLockItem = menu.findItem(R.id.menu_toggle_wifilock); + if (mWakeLock.isHeld()) { + wakeLockItem.setTitle(R.string.disable_wakelock); + } else { + wakeLockItem.setTitle(R.string.enable_wakelock); + } + if (mWifiLock.isHeld()) { + wifiLockItem.setTitle(R.string.disable_wifilock); + } else { + wifiLockItem.setTitle(R.string.enable_wifilock); + } + return super.onPrepareOptionsMenu(menu); + } + @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { @@ -635,6 +673,22 @@ private void doToggleSoftKeyboard() { imm.toggleSoftInput(InputMethodManager.SHOW_FORCED,0); } + + private void doToggleWakeLock() { + if (mWakeLock.isHeld()) { + mWakeLock.release(); + } else { + mWakeLock.acquire(); + } + } + + private void doToggleWifiLock() { + if (mWifiLock.isHeld()) { + mWifiLock.release(); + } else { + mWifiLock.acquire(); + } + } } From 1091cac89486e0b1ead4a257b8d3afdceb15a7cf Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 23 Apr 2011 11:21:27 -0700 Subject: [PATCH 038/847] Make the Terminal activity singleTask instead of singleInstance With the Terminal activity's launchMode set to singleInstance, the Preferences activity starts a new task, which can lead to some very strange back stack behavior. Set launchMode to singleTask instead, which will allow the Preferences activity into the same task. --- AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index f0acb0218..052986573 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -8,7 +8,7 @@ android:label="@string/application_terminal"> From c856059f0133b692ff543efbba6c42e2151369e7 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 23 Apr 2011 11:29:57 -0700 Subject: [PATCH 039/847] Add setComposingRegion() and getSelectedText() to our InputConnection The definition of the InputConnection interface for SDK version >= 9 includes new functions setComposingRegion() and getSelectedText(). If we don't include them in our implementation, the build fails with [javac] /home/steven/devel/android-terminal-emulator/src/jackpal/androidterm/Term.java:2955: is not abstract and does not override abstract method setComposingRegion(int,int) in android.view.inputmethod.InputConnection (at least when building with SDK 10). --- src/jackpal/androidterm/Term.java | 35 +++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index 9cfc17b98..ea8d2e98a 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -2960,6 +2960,8 @@ public InputConnection onCreateInputConnection(EditorInfo outAttrs) { private int mCursor; private int mComposingTextStart; private int mComposingTextEnd; + private int mSelectedTextStart; + private int mSelectedTextEnd; private void sendChar(int c) { try { @@ -3173,12 +3175,41 @@ public boolean setComposingText(CharSequence text, int newCursorPosition) { return true; } - public boolean setSelection(int arg0, int arg1) { + public boolean setSelection(int start, int end) { if (Term.LOG_IME) { - Log.w(TAG, "setSelection" + arg0 + "," + arg1); + Log.w(TAG, "setSelection" + start + "," + end); + } + int length = mImeBuffer.length(); + if (start == end && start > 0 && start < length) { + mSelectedTextStart = mSelectedTextEnd = 0; + mCursor = start; + } else if (start < end && start > 0 && end < length) { + mSelectedTextStart = start; + mSelectedTextEnd = end; + mCursor = start; } return true; } + + public boolean setComposingRegion(int start, int end) { + if (Term.LOG_IME) { + Log.w(TAG, "setComposingRegion " + start + "," + end); + } + if (start < end && start > 0 && end < mImeBuffer.length()) { + clearComposingText(); + mComposingTextStart = start; + mComposingTextEnd = end; + } + return true; + } + + public CharSequence getSelectedText(int flags) { + if (Term.LOG_IME) { + Log.w(TAG, "getSelectedText " + flags); + } + return mImeBuffer.substring(mSelectedTextStart, mSelectedTextEnd+1); + } + }; } From 5291627330783a06a82e3628cf6db16d580bd70a Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 23 Apr 2011 11:37:01 -0700 Subject: [PATCH 040/847] Add commitCorrection() to our InputConnection Required in order to compile under API level 10. --- src/jackpal/androidterm/Term.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index ea8d2e98a..54c65e7cc 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -61,6 +61,7 @@ import android.view.WindowManager; import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.CompletionInfo; +import android.view.inputmethod.CorrectionInfo; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; @@ -3097,6 +3098,13 @@ public boolean reportFullscreenMode(boolean arg0) { return true; } + public boolean commitCorrection (CorrectionInfo correctionInfo) { + if (Term.LOG_IME) { + Log.w(TAG, "commitCorrection"); + } + return true; + } + public boolean commitText(CharSequence text, int newCursorPosition) { if (Term.LOG_IME) { Log.w(TAG, "commitText(\"" + text + "\", " + newCursorPosition + ")"); From b8695660d3cfe5b52b2859d0f41842dd7fdea597 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 23 Apr 2011 11:39:59 -0700 Subject: [PATCH 041/847] Bump version number. --- AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 052986573..edae0a750 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,6 +1,6 @@ + android:versionName="1.0.26" android:versionCode="27"> From e1f6e9eabbaa67c8d6d11f5241e516567bc4f3a8 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 23 Apr 2011 12:15:23 -0700 Subject: [PATCH 042/847] Rename buld tool, and make it build the JNI code as well. --- tools/{br => build-release} | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) rename tools/{br => build-release} (72%) diff --git a/tools/br b/tools/build-release similarity index 72% rename from tools/br rename to tools/build-release index f9854d672..ba56f165d 100755 --- a/tools/br +++ b/tools/build-release @@ -5,4 +5,5 @@ # of Android Terminal Emulator. cd ~/code/Android-Terminal-Emulator -ant release && mv bin/Term-release.apk bin/Term.apk \ No newline at end of file +cd jni +~/code/android-ndk-r5b/ndk-build && cd .. && ant release && mv bin/Term-release.apk bin/Term.apk From f37de5004bf2c9a6ccf67d5674a7012d56ba73ce Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Mon, 25 Apr 2011 10:13:02 -0700 Subject: [PATCH 043/847] Housekeeping odds-and-ends. Make sure the release build is a clean build. Regenerate build.xml Add update.sh script for regenerating build.xml --- build.xml | 23 +++++++++-------------- tools/build-release | 1 + tools/update.sh | 1 + 3 files changed, 11 insertions(+), 14 deletions(-) create mode 100755 tools/update.sh diff --git a/build.xml b/build.xml index 7bb49e866..6ad75ae77 100644 --- a/build.xml +++ b/build.xml @@ -32,18 +32,10 @@ application and should be checked into Version Control Systems. --> - - - - - - - - + + + + @@ -60,12 +52,15 @@ --> - + + + + Geef statusbalk weer + Verberg statusbalk + + + + Geen blinkende cursor + Blinkende cursor + + + + Rechthoek + Onderlijnd + Verticale balk + + + + 4 x 8 pixels + 6 pt + 7 pt + 8 pt + 9 pt + 10 pt + 12 pt + 14 pt + 16 pt + 20 pt + + + + Zwarte tekst op wit + Witte tekst op zwart + Witte tekst op blauw + Groene tekst op zwart + Oranje tekst op zwart + Rode tekst op zwart + + + + Trackball + \@ knop + Linker Alt knop + Rechter Alt knop + Volume omhoog knop + Volume omlaag knop + Camera knop + + + + Karakter gebasseerd + Woord gebasseerd + + diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml new file mode 100644 index 000000000..445a9fa51 --- /dev/null +++ b/res/values-nl/strings.xml @@ -0,0 +1,73 @@ + + + + Terminal Emulator + Instellingen + Reset + Email naar + Speciale knoppen + Zet toetsenbord aan/uit + + Pas tekst aan + Selecteer tekst + Alles kopiëren + Plakken + + + Scherm + + Statusbalk + Statusbalk weergeven/verbergen. + Statusbalk + + Cursor stijl + Kies cursor stijl. + Cursor stijl + + Cursor blink + Kies cursor blink. + Cursor blink + + Tekst + + Lettergrootte + Kies karakter hoogte in punten. + Lettergrootte + + Kleuren + Kies tekst kleur. + Tekst kleur + + Toetsenbord + + Bedieningsknop + Kies bedieningsknop. + Bedieningsknop + + Invoer methode + Kies invoer methode voor toetsenbord. + Invoer methode + + Shell + Command line + Specificeer de shell command line. + Shell + + Oorspronkelijke opdracht + Wordt naar de shell verzonden wanneer deze gestart wordt. + Oorspronkelijke opdracht + From a162246e4c21798942d27ee645f6659df15c53f7 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Tue, 24 May 2011 00:39:15 -0700 Subject: [PATCH 048/847] Change shared library name (again) to make sure our copy takes precedence on CyanogenMod Commit 28d47b8... ("Fix java.lang.UnsatisfiedLinkError error.") changed the name of the shared library from libandroidterm to libandroidterm2, to avoid a potential library conflict with API/ABI incompatible versions being shipped in some custom firmwares. Unfortunately, CyanogenMod also uses the name libandroidterm2: https://github.com/CyanogenMod/android_packages_apps_AndroidTerm/commit/7d0de89f0ed5231a7d7eb3ca7917accb83c8eaee Change the name again, this time to something that should hopefully be unique, which should fix the problems that CyanogenMod users have been reporting in the Android Market comments. (A big scary warning to firmware distributors that the shared library ABI must not be modified without consulting upstream might be appropriate somewhere ...) Signed-off-by: Jack Palevich --- Android.mk | 4 ++-- jni/Android.mk | 2 +- src/jackpal/androidterm/Exec.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Android.mk b/Android.mk index 378b7921a..f3227ae78 100644 --- a/Android.mk +++ b/Android.mk @@ -30,11 +30,11 @@ LOCAL_PACKAGE_NAME := AndroidTerm LOCAL_MODULE_TAGS := optional -LOCAL_JNI_SHARED_LIBRARIES := libandroidterm +LOCAL_JNI_SHARED_LIBRARIES := libjackpal-androidterm2 include $(BUILD_PACKAGE) # ============================================================ # Also build all of the sub-targets under this one: the shared library. -include $(call all-makefiles-under,$(LOCAL_PATH)) \ No newline at end of file +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/jni/Android.mk b/jni/Android.mk index 7ed7785b6..7e2b62571 100644 --- a/jni/Android.mk +++ b/jni/Android.mk @@ -21,7 +21,7 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) # This is the target being built. -LOCAL_MODULE:= libandroidterm2 +LOCAL_MODULE:= libjackpal-androidterm2 # All of the source files that we will compile. LOCAL_SRC_FILES:= \ diff --git a/src/jackpal/androidterm/Exec.java b/src/jackpal/androidterm/Exec.java index 2dfcb46db..3f947611a 100644 --- a/src/jackpal/androidterm/Exec.java +++ b/src/jackpal/androidterm/Exec.java @@ -30,7 +30,7 @@ public class Exec { static { - System.loadLibrary("androidterm2"); + System.loadLibrary("jackpal-androidterm2"); } /** From b39259070aaed11a7294e041c8f640d5d830f1df Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Tue, 24 May 2011 20:15:15 +0800 Subject: [PATCH 049/847] Increment version number. --- AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 18e79a03d..f90a8c742 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,6 +1,6 @@ + android:versionName="1.0.28" android:versionCode="29"> From 3489b6909406b3d8f4941d50455e6f15ae936a3d Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Tue, 24 May 2011 20:15:51 +0800 Subject: [PATCH 050/847] Update local properties for building on a new machine. (Maybe I shouldn't check this file in at all.) --- local.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/local.properties b/local.properties index 78ec6f944..c98e8c1fa 100644 --- a/local.properties +++ b/local.properties @@ -7,4 +7,4 @@ # location of the SDK. This is only used by Ant # For customization when using a Version Control System, please read the # header note. -sdk.dir=/Users/jack/code/android-sdk-mac_x86 +sdk.dir=/Users/jackpal/code/android-sdk-mac_x86 From 1be21996ffbef85d616718abe2cf471d4ad958fb Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Wed, 25 May 2011 06:49:02 +0800 Subject: [PATCH 051/847] Use a global reference to hold onto a class object between JNI calls. This bug was detected by using CheckJNI (setprop dalvik.vm.checkjni true; stop; start). --- jni/termExec.cpp | 49 ++++++++++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/jni/termExec.cpp b/jni/termExec.cpp index f4ed7b9ca..3d87954db 100644 --- a/jni/termExec.cpp +++ b/jni/termExec.cpp @@ -60,7 +60,7 @@ class String8 { String8() { mString = 0; } - + ~String8() { if (mString) { free(mString); @@ -77,7 +77,7 @@ class String8 { } mString[numChars] = '\0'; } - + const char* string() { return mString; } @@ -104,7 +104,7 @@ static int create_subprocess(const char *cmd, const char *arg0, const char *arg1 LOGE("[ trouble with /dev/ptmx - %s ]\n", strerror(errno)); return -1; } - + pid = fork(); if(pid < 0) { LOGE("- fork failed: %s -\n", strerror(errno)); @@ -117,7 +117,7 @@ static int create_subprocess(const char *cmd, const char *arg0, const char *arg1 int pts; setsid(); - + pts = open(devname, O_RDWR); if(pts < 0) exit(-1); @@ -164,12 +164,12 @@ static jobject android_os_Exec_createSubProcess(JNIEnv *env, jobject clazz, int procId; int ptm = create_subprocess(cmd_8.string(), arg0Str, arg1Str, &procId); - + if (processIdArray) { int procIdLen = env->GetArrayLength(processIdArray); if (procIdLen > 0) { jboolean isCopy; - + int* pProcId = (int*) env->GetPrimitiveArrayCritical(processIdArray, &isCopy); if (pProcId) { *pProcId = procId; @@ -177,16 +177,16 @@ static jobject android_os_Exec_createSubProcess(JNIEnv *env, jobject clazz, } } } - + jobject result = env->NewObject(class_fileDescriptor, method_fileDescriptor_init); - + if (!result) { LOGE("Couldn't create a FileDescriptor."); } else { env->SetIntField(result, field_fileDescriptor_descriptor, ptm); } - + return result; } @@ -202,12 +202,12 @@ static void android_os_Exec_setPtyWindowSize(JNIEnv *env, jobject clazz, if (env->ExceptionOccurred() != NULL) { return; } - + sz.ws_row = row; sz.ws_col = col; sz.ws_xpixel = xpixel; sz.ws_ypixel = ypixel; - + ioctl(fd, TIOCSWINSZ, &sz); } @@ -231,7 +231,7 @@ static void android_os_Exec_close(JNIEnv *env, jobject clazz, jobject fileDescri if (env->ExceptionOccurred() != NULL) { return; } - + close(fd); } @@ -243,10 +243,19 @@ static void android_os_Exec_hangupProcessGroup(JNIEnv *env, jobject clazz, static int register_FileDescriptor(JNIEnv *env) { - class_fileDescriptor = env->FindClass("java/io/FileDescriptor"); + jclass localRef_class_fileDescriptor = env->FindClass("java/io/FileDescriptor"); + + if (localRef_class_fileDescriptor == NULL) { + LOGE("Can't find class java/io/FileDescriptor"); + return -1; + } + + class_fileDescriptor = (jclass) env->NewGlobalRef(localRef_class_fileDescriptor); + + env->DeleteLocalRef(localRef_class_fileDescriptor); if (class_fileDescriptor == NULL) { - LOGE("Can't find java/io/FileDescriptor"); + LOGE("Can't get global ref to class java/io/FileDescriptor"); return -1; } @@ -309,7 +318,7 @@ static int registerNativeMethods(JNIEnv* env, const char* className, */ static int registerNatives(JNIEnv* env) { - if (!registerNativeMethods(env, classPathName, method_table, + if (!registerNativeMethods(env, classPathName, method_table, sizeof(method_table) / sizeof(method_table[0]))) { return JNI_FALSE; } @@ -323,7 +332,7 @@ static int registerNatives(JNIEnv* env) /* * This is called by the VM when the shared library is first loaded. */ - + typedef union { JNIEnv* env; void* venv; @@ -334,7 +343,7 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) { uenv.venv = NULL; jint result = -1; JNIEnv* env = NULL; - + LOGI("JNI_OnLoad"); if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_4) != JNI_OK) { @@ -342,7 +351,7 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) { goto bail; } env = uenv.env; - + if ((result = register_FileDescriptor(env)) < 0) { LOGE("ERROR: registerFileDescriptor failed"); goto bail; @@ -352,9 +361,9 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) { LOGE("ERROR: registerNatives failed"); goto bail; } - + result = JNI_VERSION_1_4; - + bail: return result; } From df515f5cbf7574969bbd36e7f31c8827fcaa0c69 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Wed, 25 May 2011 06:51:17 +0800 Subject: [PATCH 052/847] Increment version number. --- AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index f90a8c742..3d46ede7b 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,6 +1,6 @@ + android:versionName="1.0.29" android:versionCode="30"> From c714b923cc32cf0458b789e41104dade9007ba97 Mon Sep 17 00:00:00 2001 From: Danez Date: Sat, 28 May 2011 14:36:53 +0200 Subject: [PATCH 053/847] Updated german translation. Fixed Typos. Made Email -> E-Mail, which is correct, see http://bit.ly/aWf8Af Change-Id: Idf58a1991173815e1f68bcd05f346e091bce48ea --- res/values-de/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml index 563218c3c..b80467749 100644 --- a/res/values-de/strings.xml +++ b/res/values-de/strings.xml @@ -18,7 +18,7 @@ Terminal Emulator Einstellungen Zurücksetzen - Email an + E-Mail an Spezialtasten Tastatur an/aus @@ -64,7 +64,7 @@ Shell Shell - Die zu verwendene Shell auswählen. + Die zu verwendende Shell auswählen. Shell Startkommando From 386b0e83ebfe6a792c1722c1aa70fed77763a8a9 Mon Sep 17 00:00:00 2001 From: Ladios Jonquil Date: Mon, 30 May 2011 08:09:30 +0800 Subject: [PATCH 054/847] Traditional Chinese: Update translations for v1.0.29 Change-Id: I65659be6590c067f84a10b6c8be7d3f34a1fe997 --- res/values-zh-rTW/arrays.xml | 13 +++++++++++++ res/values-zh-rTW/strings.xml | 30 +++++++++++++++++++++--------- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/res/values-zh-rTW/arrays.xml b/res/values-zh-rTW/arrays.xml index fc4874481..312e40b8b 100644 --- a/res/values-zh-rTW/arrays.xml +++ b/res/values-zh-rTW/arrays.xml @@ -82,6 +82,19 @@ + + 軌跡球 + \@ 鍵 + 左 Alt 鍵 + 右 Alt 鍵 + 提升音量鍵 + 降低音量鍵 + 相機快門鍵 + + + + + 字本 詞本 diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml index 8a5912cd4..afda0f126 100644 --- a/res/values-zh-rTW/strings.xml +++ b/res/values-zh-rTW/strings.xml @@ -22,53 +22,64 @@ 特別按鍵 顯示/隱藏鍵盤 + 使用喚醒鎖定 + 解除喚醒鎖定 + 使用 WiFi 鎖定 + 解除 WiFi 鎖定 + 編輯文字 選擇文字 全部複製 貼上 + 終端機工作階段執行中 + 螢幕 狀態列 - 顯示/隱藏狀態列。 + 顯示/隱藏狀態列 狀態列 浮標風格 - 選擇浮標風格。 + 選擇浮標風格 浮標風格 浮標閃爍 - 選擇浮標是否閃爍。 + 選擇浮標是否閃爍 浮標閃爍 文字 字型大小 - 選擇字元高度。 + 選擇字元高度 字型大小 色彩 - 選擇文字色彩。 + 選擇文字色彩 文字色彩 鍵盤 Ctrl 鍵 - 選擇 Ctrl 鍵。 + 選擇 Ctrl 鍵 Ctrl 鍵 + Fn 鍵 + 選擇 Fn 鍵 + Fn 鍵 + 輸入方式 - 選擇軟體鍵盤的輸入方式。 + 選擇軟體鍵盤的輸入方式 輸入方式 殼層 命令列 - 指定殼層的命令列。 + 指定殼層的命令列 殼層 初始命令 - 在啟動時傳送至殼層。 + 在啟動時傳送至殼層 初始命令 @@ -78,6 +89,7 @@ + From b1bfa7fbdb043a84f3302acf4d3638fadd05e237 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Sun, 9 Jan 2011 11:18:32 -0800 Subject: [PATCH 055/847] AndroidTerm: Build the JNI lib as a dependency rather than packaged Since it's installed as a system app, AndroidTerm needs its library to be located in /system/lib/ rather than within its apk. Change-Id: I528325f9745c52bd427e3f861536834270eac6fd --- Android.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Android.mk b/Android.mk index d164afea6..60628f2eb 100644 --- a/Android.mk +++ b/Android.mk @@ -32,7 +32,7 @@ LOCAL_OVERRIDES_PACKAGES := Term LOCAL_MODULE_TAGS := optional -LOCAL_JNI_SHARED_LIBRARIES := libandroidterm2 +LOCAL_REQUIRED_MODULES := libandroidterm2 include $(BUILD_PACKAGE) From bb1dc458ade25925cd98a81edfae6f28d919c823 Mon Sep 17 00:00:00 2001 From: cpasmoi Date: Sun, 29 May 2011 17:38:40 -0700 Subject: [PATCH 056/847] missing translations --- res/values-fr/arrays.xml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/res/values-fr/arrays.xml b/res/values-fr/arrays.xml index a7c51b72c..1dad65bb7 100644 --- a/res/values-fr/arrays.xml +++ b/res/values-fr/arrays.xml @@ -48,10 +48,21 @@ Touche Alt droite Touche Vol Haut Touche Vol Bas + Touche Camera + + + + Trackball + Touche \@ + Touche Alt gauche + Touche Alt droite + Touche Vol Haut + Touche Vol Bas + Touche Camera Par caractère Par mot - + \ No newline at end of file From 1d8bfb77e7de7b96506af2973502cf295a807568 Mon Sep 17 00:00:00 2001 From: cpasmoi Date: Sun, 29 May 2011 17:51:05 -0700 Subject: [PATCH 057/847] missing translations --- res/values-fr/strings.xml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml index e133ae518..c667e5626 100644 --- a/res/values-fr/strings.xml +++ b/res/values-fr/strings.xml @@ -22,10 +22,18 @@ Touches spéciales Afficher/Masquer Clavier + Activer WakeLock + Désactiver WakeLock + Activer WifiLock + Désactiver WifiLock + Modifier le texte + Selectionner le texte Tout copier Coller + Une session terminal est en cours d'exécution + Écran @@ -57,6 +65,10 @@ Choisir quelle touche utiliser pour control (CTRL) Touche CTRL + Touche Fn + Choisir quelle touche utiliser pour Fonction (Fn) + Touche Fn + Méthode d\'entrée Choisir la méthode d\'entrée du clavier virtuel Méthode d\'entrée @@ -69,4 +81,4 @@ Commande initiale Envoyée au shell au démarrage Commande initiale - + \ No newline at end of file From 1050c93c5a9049b2e00be008207b6e19ed1afa69 Mon Sep 17 00:00:00 2001 From: Patrik Kullman Date: Sun, 12 Jun 2011 09:46:37 +0200 Subject: [PATCH 058/847] Updated Swedish translations Change-Id: Ia7753c44dc2cb676d0e481b1796aeeb35c43d1be --- res/values-sv/arrays.xml | 23 ++++++++++++++++------- res/values-sv/strings.xml | 28 ++++++++++++++++++---------- 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/res/values-sv/arrays.xml b/res/values-sv/arrays.xml index 196a58e05..578b383da 100644 --- a/res/values-sv/arrays.xml +++ b/res/values-sv/arrays.xml @@ -35,15 +35,24 @@ Trackball - \@ tangent - Vänster Alt tangent - Höger Alt tangent - Vol upp - Vol ner - Kamera tangent + \@-tangent + Vänster Alt-tangent + Höger Alt-tangent + Volym upp + Volym ner + Kamera-tangent + + + Trackball + \@-tangent + Vänster Alt-tangent + Höger Alt-tangent + Volym upp + Volym ner + Kamera-tangent Teckenbaserade Ordbaserade - \ No newline at end of file + diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml index d8086d33c..1aa905577 100644 --- a/res/values-sv/strings.xml +++ b/res/values-sv/strings.xml @@ -1,44 +1,52 @@ - Terminal Emulator - Inställninar + Terminalemulator + Inställningar Återställ terminal E-posta till Special tangenter Växla till virtuellt tangentbord + Aktivera VäckningsLås + Inaktivera VäckningsLås + Aktivera WifiLås + Inaktivera WifiLås Redigera text Markera text Kopiera allt Klistra in + Terminalsessionen är igång Skärm Statusfält Visa/Dölj statusfält Statusfält - Markör stil - Välj markör stil. - Markör stil + Markörstil + Välj markörstil. + Markörstil Blinkande markör Välj blinkande markör Blinkande markör Text Teckenstorlek - Välj teckenhöjd i punkterna. + Välj teckenhöjd i punkter. Teckenstorlek Färger - Välj text färg. - Text färg + Välj textfärg. + Textfärg Tangentbord Ctrl-tangent Välj Ctrl-tangent Ctrl-tangent + Fn-tangent + Välj Fn-tangent. + Fn-tangent Inmatningsmetod Välj inmatningsmetod för virtuellt tangentbord. Inmatningsmetod Skal - Kommandoraden + Kommandorad Ange skalkommandorad. Skal Inledande kommando Skickas till skalet när det börjar. Inledande kommando - \ No newline at end of file + From 8568e0d0f313eb0d9d90fd91ec1bca4c65c90a31 Mon Sep 17 00:00:00 2001 From: Patrik Kullman Date: Sun, 12 Jun 2011 09:48:43 +0200 Subject: [PATCH 059/847] Mark non-translatable strings Change-Id: I1ce87fbc016cce8188a29604c69e56e7820b3f47 --- res/values/arrays.xml | 16 ++++++++-------- res/values/strings.xml | 20 ++++++++++---------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/res/values/arrays.xml b/res/values/arrays.xml index ab272069f..2ee0630de 100644 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -22,7 +22,7 @@ - + 1 0 @@ -33,7 +33,7 @@ - + 0 1 @@ -45,7 +45,7 @@ - + 0 1 2 @@ -65,7 +65,7 @@ - + 0 6 7 @@ -88,7 +88,7 @@ - + 0 1 2 @@ -108,7 +108,7 @@ - + 0 1 2 @@ -129,7 +129,7 @@ - + 0 1 2 @@ -145,7 +145,7 @@ - + 0 1 diff --git a/res/values/strings.xml b/res/values/strings.xml index fde9c597d..c2173216e 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -83,14 +83,14 @@ Initial Command - 0 - 0 - 0 - 10 - 2 - 5 - 4 - 0 - /system/bin/sh - - export PATH=/data/local/bin:$PATH + 0 + 0 + 0 + 10 + 2 + 5 + 4 + 0 + /system/bin/sh - + export PATH=/data/local/bin:$PATH From c66d078bde89adfd13e4a87eb1c6898a5338de8f Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 18 Jun 2011 21:17:09 +0800 Subject: [PATCH 060/847] Escape single quote character so string resource compiles. --- res/values-fr/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml index c667e5626..5c3de7e6b 100644 --- a/res/values-fr/strings.xml +++ b/res/values-fr/strings.xml @@ -32,7 +32,7 @@ Tout copier Coller - Une session terminal est en cours d'exécution + Une session terminal est en cours d\'exécution Écran @@ -81,4 +81,4 @@ Commande initiale Envoyée au shell au démarrage Commande initiale - \ No newline at end of file + From 4f369e5bd9c178db013f374a0d1484061e204f06 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 18 Jun 2011 19:37:15 +0800 Subject: [PATCH 061/847] Implement missing SGR escape codes Thanks to Sam Jacobson for the bug report! --- src/jackpal/androidterm/Term.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index 54c65e7cc..e636b0366 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -2087,6 +2087,9 @@ private void selectGraphicRendition() { code = 0; } } + + // See http://en.wikipedia.org/wiki/ANSI_escape_code#graphics + if (code == 0) { // reset mInverseColors = false; mForeColor = 7; @@ -2097,10 +2100,21 @@ private void selectGraphicRendition() { mBackColor |= 0x8; } else if (code == 7) { // inverse mInverseColors = true; + } else if (code == 22) { // Normal color or intensity, neither bright, bold nor faint + mForeColor &= 0x7; + } else if (code == 24) { // underline: none + mBackColor &= 0x7; + } else if (code == 27) { // image: positive + mInverseColors = false; } else if (code >= 30 && code <= 37) { // foreground color mForeColor = (mForeColor & 0x8) | (code - 30); + } else if (code == 39) { // set default text color + mForeColor = 7; + mBackColor = mBackColor & 0x7; } else if (code >= 40 && code <= 47) { // background color mBackColor = (mBackColor & 0x8) | (code - 40); + } else if (code == 49) { // set default background color + mBackColor = mBackColor & 0x8; // color 0, but preserve underscore. } else { if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) { Log.w(Term.LOG_TAG, String.format("SGR unknown code %d", code)); From 81ddd14c9eb46809ffee5d9819559c945a45135c Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 18 Jun 2011 19:40:54 +0800 Subject: [PATCH 062/847] Fix insert and delete line code. Thanks to Sam Jacobson for the bug report and fix! --- src/jackpal/androidterm/Term.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index e636b0366..7a039a73a 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -1043,7 +1043,7 @@ public void blockCopy(int sx, int sy, int w, int h, int dx, int dy) { || dy + h > mScreenRows) { throw new IllegalArgumentException(); } - if (sy <= dy) { + if (sy > dy) { // Move in increasing order for (int y = 0; y < h; y++) { int srcOffset = getOffset(sx, sy + y); @@ -1838,10 +1838,10 @@ private void doEsc(byte b) { break; case 'M': // Reverse index - if (mCursorRow == 0) { - mScreen.blockCopy(0, mTopMargin + 1, mColumns, mBottomMargin - - (mTopMargin + 1), 0, mTopMargin); - blockClear(0, mBottomMargin - 1, mColumns); + if (mCursorRow <= mTopMargin) { + mScreen.blockCopy(0, mTopMargin, mColumns, mBottomMargin + - (mTopMargin + 1), 0, mTopMargin + 1); + blockClear(0, mTopMargin, mColumns); } else { mCursorRow--; } From 5d499f43141865047382ca1be47e5817374b182f Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 18 Jun 2011 19:43:22 +0800 Subject: [PATCH 063/847] Allow application to be installed on the SD Card. --- AndroidManifest.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 3d46ede7b..174324eec 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,6 +1,7 @@ + android:versionName="1.0.29" android:versionCode="30" + android:installLocation="auto"> From c19d57e13d0b30fac4c6f6d931937afb7952b987 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 18 Jun 2011 21:11:42 +0800 Subject: [PATCH 064/847] Rename the rt tool to pushAndRun This tool is used to build and test the terminal emulator. --- tools/{rt => pushAndRun} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tools/{rt => pushAndRun} (100%) diff --git a/tools/rt b/tools/pushAndRun similarity index 100% rename from tools/rt rename to tools/pushAndRun From a6e99b98f39b44b25832051813243876d50a1ff0 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 18 Jun 2011 21:16:32 +0800 Subject: [PATCH 065/847] Implement a "None" setting for control and function keys. Thanks to Eli Grey for the idea. Also made the "Special keys" dialog text localizable. --- res/values/arrays.xml | 28 ++++++++++++ res/values/strings.xml | 11 +++++ src/jackpal/androidterm/Term.java | 71 +++++++++++++++++-------------- 3 files changed, 79 insertions(+), 31 deletions(-) diff --git a/res/values/arrays.xml b/res/values/arrays.xml index ab272069f..35e0834b8 100644 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -105,6 +105,7 @@ Vol Up key Vol Down key Camera key + None @@ -116,6 +117,7 @@ 4 5 6 + 7 @@ -126,6 +128,7 @@ Vol Up key Vol Down key Camera key + None @@ -137,6 +140,7 @@ 4 5 6 + 7 @@ -149,4 +153,28 @@ 0 1 + + + + Ball + \@ + Left-Alt + Right-Alt + Vol-Up + Vol-Dn + Camera + None + + + + + Ball + \@ + Left-Alt + Right-Alt + Vol-Up + Vol-Dn + Camera + None + diff --git a/res/values/strings.xml b/res/values/strings.xml index fde9c597d..a232fc059 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -93,4 +93,15 @@ 0 /system/bin/sh - export PATH=/data/local/bin:$PATH + + Control and Function Keys + + CTRLKEY Space : Control-@ (NUL)\nCTRLKEY A..Z : Control-A..Z\nCTRLKEY 0..9 : Control-0..9 + No control key set. + + FNKEY W : Up\nFNKEY A : Left\nFNKEY S : Down\nFNKEY D : Right\nFNKEY P : PageUp\nFNKEY N : PageDown\nFNKEY T : Tab\nFNKEY L : | (pipe)\nFNKEY U : _ (underscore)\nFNKEY E : Control-[ (ESC)\nFNKEY . : Control-\\\nFNKEY 2 : Control-_\nFNKEY 3 : Control-^\nFNKEY 4 : Control-] + No function key set. + diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index 7a039a73a..d0b2f8a90 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -174,18 +174,23 @@ public class Term extends Activity { private static final int[][] COLOR_SCHEMES = { {BLACK, WHITE}, {WHITE, BLACK}, {WHITE, BLUE}, {GREEN, BLACK}, {AMBER, BLACK}, {RED, BLACK}}; - private static final int[] CONTROL_KEY_SCHEMES = { + private static final int CONTROL_KEY_ID_NONE = 7; + /** An integer not in the range of real key codes. */ + private static final int KEYCODE_NONE = -1; + + private static final int[] CONTROL_KEY_SCHEMES = { KeyEvent.KEYCODE_DPAD_CENTER, KeyEvent.KEYCODE_AT, KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_ALT_RIGHT, KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_VOLUME_DOWN, - KeyEvent.KEYCODE_CAMERA - }; - private static final String[] CONTROL_KEY_NAME = { - "Ball", "@", "Left-Alt", "Right-Alt", "Vol-Up", "Vol-Dn", "Camera" + KeyEvent.KEYCODE_CAMERA, + KEYCODE_NONE }; + + private static final int FN_KEY_ID_NONE = 7; + private static final int[] FN_KEY_SCHEMES = { KeyEvent.KEYCODE_DPAD_CENTER, KeyEvent.KEYCODE_AT, @@ -193,10 +198,8 @@ public class Term extends Activity { KeyEvent.KEYCODE_ALT_RIGHT, KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_VOLUME_DOWN, - KeyEvent.KEYCODE_CAMERA - }; - private static final String[] FN_KEY_NAME = { - "Ball", "@", "Left-Alt", "Right-Alt", "Vol-Up", "Vol-Dn", "Camera" + KeyEvent.KEYCODE_CAMERA, + KEYCODE_NONE }; private int mControlKeyCode; @@ -644,30 +647,36 @@ private void doPaste() { } private void doDocumentKeys() { - String controlKey = CONTROL_KEY_NAME[mControlKeyId]; - String fnKey = FN_KEY_NAME[mFnKeyId]; - new AlertDialog.Builder(this). - setTitle("Press " + controlKey + " and Key"). - setMessage(controlKey + " Space : Control-@ (NUL)\n" - + controlKey + " A..Z : Control-A..Z\n" - + controlKey + " 0..9 : Control-0..9\n" - + fnKey + " W : Up\n" - + fnKey + " A : Left\n" - + fnKey + " S : Down\n" - + fnKey + " D : Right\n" - + fnKey + " P : PageUp\n" - + fnKey + " N : PageDown\n" - + fnKey + " T : Tab\n" - + fnKey + " L : | (pipe)\n" - + fnKey + " U : _ (underscore)\n" - + fnKey + " E : Control-[ (ESC)\n" - + fnKey + " . : Control-\\\n" - + fnKey + " 2 : Control-_\n" - + fnKey + " 3 : Control-^\n" - + fnKey + " 4 : Control-]" - ).show(); + AlertDialog.Builder dialog = new AlertDialog.Builder(this); + Resources r = getResources(); + dialog.setTitle(r.getString(R.string.control_key_dialog_title)); + dialog.setMessage( + formatMessage(mControlKeyId, CONTROL_KEY_ID_NONE, + r, R.array.control_keys_short_names, + R.string.control_key_dialog_control_text, + R.string.control_key_dialog_control_disabled_text, "CTRLKEY") + + "\n\n" + + formatMessage(mFnKeyId, FN_KEY_ID_NONE, + r, R.array.fn_keys_short_names, + R.string.control_key_dialog_fn_text, + R.string.control_key_dialog_fn_disabled_text, "FNKEY")); + dialog.show(); } + private String formatMessage(int keyId, int disabledKeyId, + Resources r, int arrayId, + int enabledId, + int disabledId, String regex) { + if (keyId == disabledKeyId) { + return r.getString(disabledId); + } + String[] keyNames = r.getStringArray(arrayId); + String keyName = keyNames[keyId]; + String template = r.getString(enabledId); + String result = template.replaceAll(regex, keyName); + return result; + } + private void doToggleSoftKeyboard() { InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); From 9c5e6f7fde932b35dd9ef85d07d9dd759044988d Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 18 Jun 2011 19:43:58 +0800 Subject: [PATCH 066/847] Increment version number. --- AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 174324eec..00934a830 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,6 +1,6 @@ From cd9a67afce18d0a51ceff6e9a308f01382ba9e37 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 25 Jun 2011 19:35:39 +0800 Subject: [PATCH 067/847] Update Italian localization Thanks to fireb33@gmail.com --- res/values-it/arrays.xml | 46 +++++++++++++++++++++++++++++++----- res/values-it/strings.xml | 49 +++++++++++++++++++++++++++------------ 2 files changed, 74 insertions(+), 21 deletions(-) diff --git a/res/values-it/arrays.xml b/res/values-it/arrays.xml index 1c0c48cda..25a663b05 100644 --- a/res/values-it/arrays.xml +++ b/res/values-it/arrays.xml @@ -22,7 +22,7 @@ - Cursore non lampeggiante + Cursore fisso Cursore lampeggiante @@ -56,11 +56,24 @@ Trackball - Tasto \@ - Tasto Alt sin. - Tasto Alt des. - Pulsante Vol + - Pulsante Vol - + \@ + Alt sin. + Alt des. + Volume + + Volume - + Fotocamera + Nessuno + + + + Trackball + \@ + Alt sinistro + Alt destro + Volume + + Volume - + Fotocamera + Nessuno @@ -68,4 +81,25 @@ Word-based + + T.ball + \@ + AltSX + AltDX + Vol+ + Vol- + Camera + Ness. + + + + T.ball + \@ + AltSX + AltDX + Vol+ + Vol- + Camera + Ness. + diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml index 20fbccc4b..d47551a85 100644 --- a/res/values-it/strings.xml +++ b/res/values-it/strings.xml @@ -15,22 +15,29 @@ * limitations under the License. --> - Emulatore Terminale + Emulatore terminale Preferenze - Resetta term - Invia Email - Combinazione tasti - Attiva/disattiva keyboard + Reset term. + Invia email + Tasti speciali + Attiva/disattiva tastiera + + Attiva WakeLock + Disattiva WakeLock + Attiva WifiLock + Disattiva WifiLock Modifica testo + Seleziona testo Copia tutto Incolla - + Terminale in esecuzione + Schermo Status bar - Mostra/nascondi status bar. + Mostra o nascondi la status bar. Status bar Stile cursore @@ -38,7 +45,7 @@ Stile cursore Lampeggio cursore - Scegli lampeggio cursore. + Scegli il lampeggio del cursore. Lampeggio cursore Testo @@ -47,18 +54,22 @@ Scegli l\'altezza dei caratteri in punti. Dimensione carattere - Colori + Colore Scegli il colore del testo. Colore testo - Tastierra + Tastiera + + Tasto Control + Scegli il tasto Control. + Tasto Control - Tasto control - Scegli tasto control. - Tasto control + Tasto Fn + Scegli il tasto Fn. + Tasto Fn Metodo inserimento - Scegli il metodo di inserimento per la tastiera virtuale. + Scegli il metodo di inserimento per la tastiera. Metodo inserimento Shell @@ -67,7 +78,15 @@ Shell Comando iniziale - Inviato alla shell al suo avvio. + Inviato alla shell all\'avvio. Comando iniziale + Tasti Control e Fn + + CTRLKEY spazio : Control-@ (NUL)\nCTRLKEY A..Z : Control-A..Z\nCTRLKEY 0..9 : Control-0..9 + Nessun tasto Control impostato. + + FNKEY W : Su\nFNKEY A : Sinistra\nFNKEY S : Giù\nFNKEY D : Destra\nFNKEY P : Pag.Su\nFNKEY N : Pag.Giù\nFNKEY T : Tab\nFNKEY L : | (pipe)\nFNKEY U : _ (underscore)\nFNKEY E : Control-[ (ESC)\nFNKEY . : Control-\\\nFNKEY 2 : Control-_\nFNKEY 3 : Control-^\nFNKEY 4 : Control-] + Nessun tasto Fn impostato. + From 28025150c0624121c6b1915f1a717118933a1162 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 25 Jun 2011 19:45:32 +0800 Subject: [PATCH 068/847] Enable an initial command string to be passed as part of the intent. Thanks to Christoph Schmidt-Hieber, M.D., c.schmidt-hieber@ucl.ac.uk for the idea and the patch. --- src/jackpal/androidterm/Term.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index d0b2f8a90..1f577f263 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -313,6 +313,18 @@ public void run() { mEmulatorView.initialize(mTermFd, mTermOut); + /* Check whether we've received an initial command from the + * launching application + */ + String iInitialCommand = getIntent().getStringExtra("jackpal.androidterm.iInitialCommand"); + if (iInitialCommand != null) { + if (mInitialCommand != null) { + mInitialCommand += "\r" + iInitialCommand; + } else { + mInitialCommand = iInitialCommand; + } + } + sendInitialCommand(); } From 290aaf22e6a2a729a98e5ea4c094d7f16106d008 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 25 Jun 2011 19:50:56 +0800 Subject: [PATCH 069/847] Add link to Downloads page to README file. --- README.md | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 3825795cf..a423eac33 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,21 @@ #Android Terminal Emulator -Android Terminal Emulator is a terminal emulator for communicating with the built-in Android shell. Emulates a reasonably large subset of Digital Equipment Corporation VT-100 terminal codes, so that programs like "vi", "Emacs" and "NetHack" will display properly. +Android Terminal Emulator is a terminal emulator for communicating with the built-in Android shell. +Emulates a reasonably large subset of Digital Equipment Corporation VT-100 terminal codes, so +that programs like "vi", "Emacs" and "NetHack" will display properly. -This code is based on the "Term" application which is included in the Android source code release. It's provided as a separate project for the convenience of developers who do not want to deal with installing and building the whole Android source tree. +This code is based on the "Term" application which is included in the Android source code release. +It's provided as a separate project for the convenience of developers who do not want to deal with +installing and building the whole Android source tree. -Although this program does not include a built-in ssh client, it can be used with command-line-based ssh tools such as dropbear. +Got questions? Please check out the +[FAQ](http://github.com/jackpal/Android-Terminal-Emulator/wiki/Frequently-Asked-Questions) +before emailing or adding an issue. Thanks! -Got questions? Please check out the [FAQ](http://github.com/jackpal/Android-Terminal-Emulator/wiki/Frequently-Asked-Questions) before emailing or adding an issue. Thanks! +Please see the +[Recent Updates](http://github.com/jackpal/Android-Terminal-Emulator/wiki/Recent-Updates) +page for recent updates. -Please see the [Recent Updates](http://github.com/jackpal/Android-Terminal-Emulator/wiki/Recent-Updates) -page for recent updates. \ No newline at end of file +Not on Market? Don't want to compile your own version? No problem. A fairly recent version of +Android Terminal Emulator is here: +[Download Prebuilt version](https://github.com/jackpal/Android-Terminal-Emulator/downloads) From 892831647b99de29af04a953eb78e3be18049049 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 25 Jun 2011 20:00:54 +0800 Subject: [PATCH 070/847] Make Ctrl/Fn-[A-Z] work with uppercase as well as lowercase letters Patch courtesy Steven Luo steven+android@steven676.net Steven says: For Ctrl-[A-Z], this matches the behavior of xterm running on my Debian box; for Fn-[A-Z], there are no possible compatibility issues and it seems like the most natural thing to do. --- src/jackpal/androidterm/Term.java | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index 1f577f263..1b373b89a 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -4424,6 +4424,8 @@ public int mapControlChar(int ch) { // Search is the control key. if (result >= 'a' && result <= 'z') { result = (char) (result - 'a' + '\001'); + } else if (result >= 'A' && result <= 'Z') { + result = (char) (result - 'A' + '\001'); } else if (result >= '0' && result <= '9') { result = (char) (result - '0' + '\001'); } else if (result == ' ') { @@ -4440,25 +4442,25 @@ public int mapControlChar(int ch) { result = 31; } } else if (mFnKey.isActive()) { - if (result == 'w') { + if (result == 'w' || result == 'W') { result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_UP; - } else if (result == 'a') { + } else if (result == 'a' || result == 'A') { result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_LEFT; - } else if (result == 's') { + } else if (result == 's' || result == 'S') { result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_DOWN; - } else if (result == 'd') { + } else if (result == 'd' || result == 'D') { result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_RIGHT; - } else if (result == 'p') { + } else if (result == 'p' || result == 'P') { result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_PAGE_UP; - } else if (result == 'n') { + } else if (result == 'n' || result == 'N') { result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_PAGE_DOWN; - } else if (result == 't') { + } else if (result == 't' || result == 'T') { result = KEYCODE_OFFSET + KeyEvent.KEYCODE_TAB; - } else if (result == 'l') { + } else if (result == 'l' || result == 'L') { result = '|'; - } else if (result == 'u') { + } else if (result == 'u' || result == 'U') { result = '_'; - } else if (result == 'e') { + } else if (result == 'e' || result == 'E') { result = 27; // ^[ (Esc) } else if (result == '.') { result = 28; // ^\ From 29e95e964ec41b8c8de1832950b35cb165eba380 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 25 Jun 2011 20:03:30 +0800 Subject: [PATCH 071/847] Fix behavior of Ctrl-[0-9] Patch contributed by Steven Luo. steven+android@steven676.net Steven writes: In xterm on my Debian system, the Ctrl-[0-9] combinations appear to map to the following: Ctrl-2: ^@ (NUL) Ctrl-3: ^[ (ESC) Ctrl-4: ^\ Ctrl-5: ^] Ctrl-6: ^^ Ctrl-7: ^_ Ctrl-8: \177 (DEL) Ctrl-1, Ctrl-9, and Ctrl-0 appear to send 1, 9, and 0 respectively. I have no idea whether anyone actually seriously uses these, but make these key combinations behave in that manner, and remove the bogus Ctrl-[0-9] and superfluous Fn-[234] combos. --- src/jackpal/androidterm/Term.java | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index 1b373b89a..c16d9498f 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -4426,20 +4426,20 @@ public int mapControlChar(int ch) { result = (char) (result - 'a' + '\001'); } else if (result >= 'A' && result <= 'Z') { result = (char) (result - 'A' + '\001'); - } else if (result >= '0' && result <= '9') { - result = (char) (result - '0' + '\001'); - } else if (result == ' ') { + } else if (result == ' ' || result == '2') { result = 0; - } else if (result == '[') { - result = 27; - } else if (result == '\\') { + } else if (result == '[' || result == '3') { + result = 27; // ^[ (Esc) + } else if (result == '\\' || result == '4') { result = 28; - } else if (result == ']') { + } else if (result == ']' || result == '5') { result = 29; - } else if (result == '^') { + } else if (result == '^' || result == '6') { result = 30; // control-^ - } else if (result == '_') { + } else if (result == '_' || result == '7') { result = 31; + } else if (result == '8') { + result = 127; // DEL } } else if (mFnKey.isActive()) { if (result == 'w' || result == 'W') { @@ -4464,12 +4464,6 @@ public int mapControlChar(int ch) { result = 27; // ^[ (Esc) } else if (result == '.') { result = 28; // ^\ - } else if (result == '4') { - result = 29; // ^] - } else if (result == '3') { - result = 30; // control-^ - } else if (result == '2') { - result = 31; // ^_ } } From cb368f0f482a0e20ba3096fd90d9810878456434 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 25 Jun 2011 20:07:44 +0800 Subject: [PATCH 072/847] Add F1-F12 and Ins/Del/Home/End keys Contributed by Steven Luo steven+android@steven676.net who writes: The mapping is as follows: Fn-[1-9]: F1-F9 Fn-0: F10 Ctrl-9: F11 Ctrl-0: F12 Fn-I: Ins Fn-X: Del Fn-H: Home Fn-F: End It should be noted that a real VT100 does not have Home/End keys, and as such, the terminfo entry for TERM=vt100 doesn't contain escape codes for Home and End. This bothers some applications, but not others. The codes generated match those sent by the Linux console (TERM=linux), which appears to work fine as a TERM setting since the emulation fixes in 1.0.30. xterm sends different escape codes, though; I left the escape codes at their present values because TERM=xterm causes problems with some applications. Addresses GitHub issue #9. --- I'm unhappy with the reintroduction of different behaviors for the Ctrl key, but I couldn't think of another choice that's guaranteed to make sense on any soft keyboard layout. (Fn-J/K and Fn-(/) are the two other possibilities that occurred to me.) Feel free to change. --- src/jackpal/androidterm/Term.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index c16d9498f..fabe014a2 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -4440,6 +4440,10 @@ public int mapControlChar(int ch) { result = 31; } else if (result == '8') { result = 127; // DEL + } else if (result == '9') { + result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_F11; + } else if (result == '0') { + result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_F12; } } else if (mFnKey.isActive()) { if (result == 'w' || result == 'W') { @@ -4464,6 +4468,19 @@ public int mapControlChar(int ch) { result = 27; // ^[ (Esc) } else if (result == '.') { result = 28; // ^\ + } else if (result > '0' && result <= '9') { + // F1-F9 + result = (char)(result + KEYCODE_OFFSET + TermKeyListener.KEYCODE_F1 - 1); + } else if (result == '0') { + result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_F10; + } else if (result == 'i' || result == 'I') { + result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_INSERT; + } else if (result == 'x' || result == 'X') { + result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_FORWARD_DEL; + } else if (result == 'h' || result == 'H') { + result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_MOVE_HOME; + } else if (result == 'f' || result == 'F') { + result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_MOVE_END; } } From 839ac83fd224db75e9db83f720a59af43093276e Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 25 Jun 2011 20:09:29 +0800 Subject: [PATCH 073/847] Update in-app documentation for special keys Contributed by Steven Luo steven+android@steven676.net --- res/values/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index a232fc059..a1fb0a763 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -97,11 +97,11 @@ Control and Function Keys - CTRLKEY Space : Control-@ (NUL)\nCTRLKEY A..Z : Control-A..Z\nCTRLKEY 0..9 : Control-0..9 + CTRLKEY Space : Control-@ (NUL)\nCTRLKEY A..Z : Control-A..Z\nCTRLKEY 5 : Control-]\nCTRLKEY 6 : Control-^\nCTRLKEY 7 : Control-_\nCTRLKEY 9 : F11\nCTRLKEY 0 : F12 No control key set. - FNKEY W : Up\nFNKEY A : Left\nFNKEY S : Down\nFNKEY D : Right\nFNKEY P : PageUp\nFNKEY N : PageDown\nFNKEY T : Tab\nFNKEY L : | (pipe)\nFNKEY U : _ (underscore)\nFNKEY E : Control-[ (ESC)\nFNKEY . : Control-\\\nFNKEY 2 : Control-_\nFNKEY 3 : Control-^\nFNKEY 4 : Control-] + FNKEY 1..9 : F1-F9\nFNKEY 0 : F10\nFNKEY W : Up\nFNKEY A : Left\nFNKEY S : Down\nFNKEY D : Right\nFNKEY P : PageUp\nFNKEY N : PageDown\nFNKEY T : Tab\nFNKEY L : | (pipe)\nFNKEY U : _ (underscore)\nFNKEY E : Control-[ (ESC)\nFNKEY X : Delete\nFNKEY I : Insert\nFNKEY H : Home\nFNKEY F : End\nFNKEY . : Control-\\\n No function key set. From 804f3b5fa145448d89867d8c34e778910bf10b35 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 25 Jun 2011 20:11:04 +0800 Subject: [PATCH 074/847] Increment version number. --- AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 00934a830..049286d99 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,6 +1,6 @@ From a6f3ac348bcbe64ccadfc4a4962572c1b9ceda02 Mon Sep 17 00:00:00 2001 From: Ondrej Zima Date: Tue, 16 Aug 2011 10:27:44 +0200 Subject: [PATCH 075/847] Added Czech translation. Change-Id: I42fc8a3c56c5ce7fa457a3f1b7f54c875cb9ce2b --- res/values-cs/arrays.xml | 81 ++++++++++++++++++++++++++++++++++++++ res/values-cs/strings.xml | 83 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 164 insertions(+) create mode 100644 res/values-cs/arrays.xml create mode 100644 res/values-cs/strings.xml diff --git a/res/values-cs/arrays.xml b/res/values-cs/arrays.xml new file mode 100644 index 000000000..8b04a973e --- /dev/null +++ b/res/values-cs/arrays.xml @@ -0,0 +1,81 @@ + + + + + + Zobrazit stavovou lištu + Skrýt stavovou lištu + + + + Kurzor nebliká + Kurzor bliká + + + + Obdélníkový + Podtržení + Svislá čára + + + + 4 x 8 pixelů + 6 bodů + 7 bodů + 8 bodů + 9 bodů + 10 bodů + 12 bodů + 14 bodů + 16 bodů + 20 bodů + + + + Černý text na bílé + Bílý text na černé + Bílý text na modré + Zelený text na černé + Jantarový text na černé + Červený text na černé + + + + Kulička + Klávesa \@ + Klávesa levý Alt + Klávesa pravý Alt + Klávesa Hlas+ + Klávesa Hlas- + Klávesa kamery + + + + Kulička + Klávesa \@ + Klávesa levý Alt + Klávesa pravý Alt + Klávesa Hlas+ + Klávesa Hlas- + Klávesa kamery + + + + Po znacích + Po slovech + + diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml new file mode 100644 index 000000000..5521ca932 --- /dev/null +++ b/res/values-cs/strings.xml @@ -0,0 +1,83 @@ + + + + Emulace terminálu + Nastavení + Obnovit term. + Poslat e-mailem + Spec. klávesy + Skrýt/zobrazit klávesnici + + Zamezit usnutí + Povolit usnutí + Zamezit odpojení WiFi + Povolit odpojení WiFi + + Změnit text + Vybrat text + Kopírovat vše + Vložit + + Terminálová relace je spuštěna + + + Obrazovka + + Stavový řádek + Zobrazit/skrýt stavový řádek. + Stavový řádek + + Styl kurzoru + Zvolit styl kurzoru. + Styl kurzoru + Blikání kurzoru + Zvolit blikání kurzoru. + Blikání kurzoru + + Text + + Velikost fontu + Zvolit výšku znaku v bodech. + Velikost fontu + + Barvy + Zvolit barvu textu. + Barva textu + + Klávesnice + + Řídící klávesa + Zvolit řídící klávesu. + Řídící klávesa + + Funkční klávesa + Zvolit funkční klávesu. + Funkční klávesa + + Metoda vstupu + Zvolit metodu zadávání pro klávesnici. + Metoda vstupu + + Shell + Příkazový řádek + Zvolit příkaz pro shell. + Shell + + Inicializace + Provést po spuštění shellu. + Inicializace + From 3f50ffa6a63b04af3e52128f05d56bbcc8a76315 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sun, 21 Aug 2011 16:44:04 -0700 Subject: [PATCH 076/847] Updated translations Thanks to fireb33@gmail.com --- res/values-it/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml index d47551a85..32d3e5481 100644 --- a/res/values-it/strings.xml +++ b/res/values-it/strings.xml @@ -16,11 +16,11 @@ --> Emulatore terminale - Preferenze - Reset term. + Impostazioni + Reset terminale Invia email Tasti speciali - Attiva/disattiva tastiera + Mostra/nascondi tastiera Attiva WakeLock Disattiva WakeLock From dd43ce4ea9e52422e615ed266c1e6b451a41b761 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Mon, 22 Aug 2011 20:39:40 -0700 Subject: [PATCH 077/847] Improve market icon. Shinier, and more like a terminal. --- docs/Market Icon.psd | Bin 277317 -> 339784 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/Market Icon.psd b/docs/Market Icon.psd index 90b6f7723e9297dc2ccc2f3ef3d65502908d81a0..923d3479dd0713637dd7542ab5ca7999626973d4 100644 GIT binary patch literal 339784 zcmeD^31AaN*ORuCwiGCrf}lhM0j140y*YbB<*KxR2neA`+d!L?B;`^mc;Strf(Kef z5dj4i1uv|MiU)`ypn_Ty5xG(Bquu|_Z1&9VCfVrsdu$=u-M91Rc=O(yH*eld(z6ST zkO47&5eS9B9ekotI0KqJDLuPjXau~0zownwRT%suuxi^9rr12jNwzY(qhFV!Z`XA(+O7S%j7}L!4Xw(tmD>l+a@mSz z4J$60HKioO+GRlhn7%W5�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%0Dj}1hzeApn0v>_I4T0Z#A&pM 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≪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 literal 277317 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 J`r-_y{y#r;gUtW{ From 029570ade268791590e6518538e93f4547ef7ecc Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Mon, 22 Aug 2011 20:53:29 -0700 Subject: [PATCH 078/847] Shrink market icon to match "safe" bounds See http://www.google.com/support/androidmarket/developer/bin/answer.py?answer=1078870 --- docs/Market Icon.psd | Bin 339784 -> 304537 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/Market Icon.psd b/docs/Market Icon.psd index 923d3479dd0713637dd7542ab5ca7999626973d4..45379225411aea482b3e9249e93fa64dd4220367 100644 GIT binary patch literal 304537 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 z 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 Zcpa6IXio|9m1wd0n;H#ugBI)t?Y~U5ka7S3 literal 339784 zcmeD^31AaN*ORuCwiGCrf}lhM0j140y*YbB<*KxR2neA`+d!L?B;`^mc;Strf(Kef z5dj4i1uv|MiU)`ypn_Ty5xG(Bquu|_Z1&9VCfVrsdu$=u-M91Rc=O(yH*eld(z6ST zkO47&5eS9B9ekotI0KqJDLuPjXau~0zownwRT%suuxi^9rr12jNwzY(qhFV!Z`XA(+O7S%j7}L!4Xw(tmD>l+a@mSz z4J$60HKioO+GRlhn7%W5�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%0Dj}1hzeApn0v>_I4T0Z#A&pM zcF`=OSJBL<$mA3`h-5$t5SPmSu}sQ7<0~srLRf)ophQ<;2&8C^Q;7&6*Px;j7uGZZ zRxnr%jdK?9_`o?BME(X=Oy@|SomZTPfkma+L+~|cN~KqG&LG`|Vfb%SMYW6J1|V;5 z28O?j%B{oXxMY~qZIgp%IXqdcS%k_erWdh1P%yBF^nH=5gj5KNmzd3p%B%3eMVMBk znumZ2cYqSd+eXM=j%%7xUIpa13bCr?7#}Mh28a2^OfQ6qYaoX$>=+hX2rF<@c?kyt z;4p7lWc(yjW4ISUP4RO{CDBeqiojgJAgkMoO|m5TgfT-B+|7Uw8J^%u{&335>Ix$= z)UhB2-5wgc6S@a7#O@Wobh6##-StcXZp^|^D4y}NFgt^FHNheqI&h_M3T)cox?(KD z3}~VdILS5*^BX2m7}rE-CX2u|lEcE;GJj!pb;KNF^JC#&Z1^p5fOLTmv2C&K#n@Rm z9n=YO#CP})molx=Jq0W{u*aB@0{dYU{sd~qCq)!g3EJrMXF%=G-ijh0@Q`cFi8AiW zyg9k4f-LvRi771sz~3C$|19hk;#;nNh{bmbt5Rm9Z?`w=ir zD;#btnh|a=UCHxIyA?mfCm%nTR7}FXAAt#mpW$2>%;v-*!p~D2Q?Q-OJy*D^N=45l zWZ>ZX1LKs=a8*H?xc91!$LT#cznR%{&v*6Lu6Ymo_Br@0gU?#{Y-B8T^dh815c!O& za1Buq*Wg@uOTFnPk|KCZJsIcoXIbh4=KXf@Z{CWq163x_E_^owWCyAN&fT3c2$#)R z>fsMCDP$~lLRSUrLCI1N%nTq!{^e#swzp+)3C#@1gdAG#hA;Tzs0;TjjHPa-02ZPd zz)o5IIy*&V28?(Qs?Pj{YzAO)_x|Z-fO9uX3*^@fC}5Hj+58V!0U~P!H=R|ihC0Cr ziGlwiD`4x2m1x_V=?Gu9KkzMgHITcXhW8L%f9`b`j7(qWpF%(HMrd2p4@6f3`gZd} z1j1gwyMH2iHlUqXtwb0bUvaMNm3f0p#t`<3Yh*wedmHo<&P{kVkdiS3*8pE}jqsHj zXOygfWbb=rD}Y2G|D{$y<%MTf_n7j@dzO;jXZquvZwRbB1Tz`KL-6^8u>x9uhTGC? z0k#6*8>R`(7O>13qQB0J^ZB!^0O16K8>a*sh#RN8FkH6oulxV=*9|!MOUGX@Yr!Ae zUjq*1yp!`Z0deky%qdT!h)f11^Z|i!@IUj{F{Lx#Y+Z#gjHAF-Gf9$U-oWs>UWJBG| zWu^P;ghQuaL;b(zuWQXR3hPS4h6C}}y#N-G5cnUm)KjlsfQ;WZAbjomQZw#qAa~!l zeL!@r|LrNT9y5$>(4O#)sP9L4qN@RY&^?_%*y~4|CX;6a`sBTX2xH?b&Xv6~Z*a*N z!d`KW3+uB>bEmksw>Ey57UIu@7lXRj6&W}=o)34E(IraHL8Fmd-C_UT@&&;gK#B7^r`--rrKbJ zZ1_7gpDLdZ&x=AB%ZogW$lyQIFP4>w|1nCGjGJ|^MlXPgR>wHM7#>*J^)rlxuwP6F z{QHcy#WRb_L8bOUhRoKB?zsN`p9H>lrU$+7oXp}@re9+Ye4jaGJq;49W7?7J+7gD8b8M-i+9qG)mqnZs(EAqatq$#dX?*>WxLQ#n?T1)(cTVq*IC zjfpmSU%(IGz)uPR03dKukkbWs%d)1%n9=l zg6Lt=vB^wXGwg2TSo{l4MhZUoI^LW0bz|TK2I3C`BrRjw7j1$zWZ_>oGLFRv-W>U6 z|0&IgHeCZJd3w-@>pCRUvQqo>I*Ew zr%q+tN}Vp7C<_eEDuuW;Av6mP=^He&svL%JkIm?DRomDiIjD8C(`7BP&$1a)*>{C5 z8}r&|=0HxjM+$>usNA-aYL9)IH@VxG&ZfT}j-xXsrSeTAr9d6=`Bh_$3F)NY5)+b= z*v^YKF-NrG_A!arj^sNNcywR|G>1=vd!PMyxggqvTRp^P!D0z7xi)w~*dN3)W3*|I z11~;ZKgf$houW{NTHn9T2GNHzdq!Y0WP|A4` zCJpA~kcYE+ffFgVxd8|tpG)AmgDk=Cr}4~*p9lwMBqXM!m~k`lpDEu`JtNy$VHNfV z11*4GU$d8p;X0=!&#_dw`8+X%P0m9{g2r*-9Kb(oOe_d| zKMny|tcD*H;VVAOnGJ_^PZ8G*;@vAq2)nnRS&oDrC$i0!9c>zAsi=k~FoOlFCh(P2 z6<+BCOrV8x3KJP0T(${mR7!f95iW@ta7oQbr+A&l=Z`Cmf7MPzPo_`;7SWR=#>0gu z^%%Yz*(RF6tj7I1+HUof^K9rXRw#b#U>AbCas0^1o0&>TOrt1H>vIApWM>T2j!$%JnJ8^TcR&;Ys zWXJ;(B|9XxxQ*DVHjQ-K@?liP1CFJF7=}a-a$}Zh1pJ;XoSkar$?4r>#EefsN!YDI zrWb78N5j4+7?1gZj8S@~oN{}G6;5GgW)AGs0Yn<-Aham)^ac*$#;5tS!6~KsnyKC@143cB)8llPJFB3b_>X%dQ$L&>>_K8A_)L}n z*MzMHZ$5&IWOIV?7VjN{sYoTslk$$VLW3Q06emjbmdbGzsj?)IS*~g<6bPz!XM~=HyqSkV#^gwh`lyE?ByeU$4gvz3Fs61*QRX~+cw^FxL^Qil&XQ*eXrPMNNIkkb>L+z#ZQAeng)G6u- zYB3yRb~zf?6OBhVqh2ThLPYvp9)5W=T0%E5Qf!@I+K6jpY;WvDM^SC*KJ-{Bii)9P zsTNcm8rK7jMK{uW=?L^JZA8nc)^s?7IoM{aB8csb-53ZvIr6p-3haen5F!UlxFR56 zDe|zDR3V%{qOn=1wv$N|T$3;dnT9$i+XurD-Vz3ZT~lDHTPjo-B^3>YHcke184Qe4 z3D+WU^?FdElx891W&RWMO8OJ?O6Kw+L@X}14a2{8UBR21L>rp87o4w&cMeG$F_I48 z2tN#n>;m#yz<+K;BVemsHZEMzQo&_KVO;CgulglHeAL+zBCN=IcA~<}dLakOmMHCQv9oUISU5fkadYCo5P;xuAWW zFt}M^cw>GP2@iV?SGk9xU%c*O7T-LP|QBVioal7;cy!odBo3 z_uV8}3@_9-J=g;kpg))b(!4f$uCoL@gbojDtHVTz2n(z1m2|aksM9dN&<%CnP>01_ zUtv(1jOd0s9+<$LA+wCC8|s1uQ`Yp<4R!HKp>C*qg9vm(om)sGwkDnc`YiJ4hB{WZ ziqinj2xaC+ycsmo-E4?Pcnf-E%A#&n1fW|2!d%g$?LX~*TeBSb&@nOIL!zr1jVtxL+TT02L%cK zNGvC_FBAtlhqJML1kRrVk%^!G(x`H0=Le%U8{#sX{CJ^xX7!y=7rg%yejU;f1t0Gz z5Ham=)g!y&et>U?Dv#`nYX);L$1~)-w%DMf%Dv~cCEeHdL$e* z$WtKx8|Sr!OO62w{=Bx7FA_g961sEyKQjlr@3qchU~CL@rez{(;e}zNkd0XVU+(|R zZ2iixmok}pv0LjhjWTCoT=LS0rQURb)fL1E{{Gan%^~!_U&8kPGtZVIO(8>}H`tp!TkcQY0Kp$tDcS(>ciO8;2=OmF zTdpf<6w}4}*>bx%YUq1a;aD)?*>W)~X@uKSv7v_k!)ME-UNr}GX?FzS>y@Q*xGU^c z1<0;A2EtzJEqMQA#_}U@R?HmK@7x^G)qrlyMg+oMYZfdZ&jxgBYlJX1zT({2EAs}I zj3Mk5*T{e{_GJs_kX(gV11T9pa1HPk*9c$L_o~9Nh-4O_yjQhIZB_v$KEV&=Y`M&i zzY#9XOeEWw@IKKP_;(4Mgx40ehJS6m|5~F~U~x15TEfp^=>K#I{MRVJaDOY2RA32d zhuQ-yW-k1|{Dbg03m^9G6pFaa{>rA~57Kk#2kE=$h;KLDJt(pf(wu?c57=}s0pCuC zpSrIdz|J8<>Zf-T*sZkT_*@iu4xL8^*M++XY%Xosa@l!w0inC+c>!VczOENg101S) zhZjQAhF$F%P$TRdyxj|Gqzxa3H^NDwhR9tktbs;$3_>$*^QLN`!wY7m3gt{iwHKDEv|MQw~ zHu#i}34F>fc-|^_E{5kNf@d5LcRl+IPVAqspS$I8fuDOWoL30Hoz6nQ8GfoG1{vBP z7P!pkHDbs5coqVV^{wE{2lx9tf&2ZJjt$sHzl8VEACA9({rEEke*CWIvA4hPF2>t` z3ymH^(dS?ZAS|+C7q=>KVJQWmQz)F8{oa8Gh|l~^I(*BL;?{Tt;dg}EEl8T1^kC9G zNr*ZF5PsbO+Kbom^nW@9bP5DXfw|trsk3lpys`S-Tw14rP6NC$q4R-G1Dy}_aX}v& zbxo*iLR}N;no!q-x+c^$;eSaJ>fSZov#3)*8wK>yQXeh#(el4ETIv$7OS~@ey2R_A z0$mg8no!q-x+c^$p{@yaO{k;^b?=%s&x($vQ$W|Nx?WY%t2%G!yrJ`kKKsyTAG#*g zHKDEvbxo*iLR}N;no!q-|0PWb-nB3ki=t5!YU=YZ3ZOCY7B>`a zvE)}e?ATRsvu`hLSPJJ5{6ZTxXuiOKkdV!LkliM@-Uk(m-?fhpTOW-}jm$VuYM2<` zh+va0uB+dFKS4@={{qZu_fLNR1(=iH8j;pT!ReuG;e$`wZRPzJ=RI4u1=9cN6!4`0 zbLws!;TDE#@ENpi;S|~^y#ISHIu1L=!`h8@&3|m}W4Anp?lYA3J&AXghnIYecb3nE zmO=hE;9>cxPrRM6PilJ-8NSKQ{xoPVuJZ&A!J#NR3?YYNV zG9&(!n-O12o+ols=AXjJS#;rBm}xdls!Q=nbcVhcn29*dyWfL(HylLgXvt=oYy?8~Jl7ple=T^Xi({zvk8DM3)m?PINiZ^? zRL@Pz#XXL`LvUYno<^B}!F?gl^8^YvPOPmvO-Iz%&9BOQ20*7#n6ak*FdbG` zMJ4h;v-CM1G}taB&8bifio;bCf}k<=0HcJ}0$457bxRx&i>nv_GojV>6x(Xt0Jw)9 z3a>Tt=>SBXr0LZxZ$KVZN4$K3JL0JK9x73Fj1JpUWn2syLElmJM}eq?Pr=g}WT-g` zPxTX@h9~IuIPc>UcsvP@6Su(II_f!i`~&)Ce*H1%cFYo(E1`s1OmKV;CD-C!VYt;$ zTYu8q_Y5FB6JYrfv^vaItr9JDg;7ZR$RT%%06(35B)E+dSp&4f@n(xFgq=uBk9@Ftlw4ZQ#X zg5(vnvkLoXU}zZr~mdvJe03->gx>e1hT875+> zzktFsYwC_3qr>ZKC&twR1mXkZGAB0H&~>OD*sSU=P(l9GTH*ORMB#GhdOr_CYmV0J ztU;M@2Q0V~I6%{#@o47GOk_j`qJXgy{)haVOMeI9a{A}5qJhp4e-&l_srl+0@lWl! zzpCEA5m97f(25-C&>AKPad3yf@#H}N*MiWZ6`=|~^aB5jg9%U^dw9Vqywf0ZFtgL( zj>jVAmF6Kh&j4)D!K;qn4+Gq-wBguFlzke{J~BSOheU{n(Qi>_Vaj2Me2CfG&`2BR zhMx!1#SpumInAJfHmr)qXBQYE*E2gJF3^To!W-~}ZbFjTp$z?94bzhZU2Z8e6#M^>{PHRm{c?czvNgJldH@u->nSr^mRbyWwd) z_p}n8KINX?f~T$A(+2$s`*eVUIwc6l>y*%?Lzf3#EB4sS8dD%`4zL##OL&QZ z(;Cy{z!{Aha^OCVedNIX8vDtC2Q&_l17|g6$$@hkb7a7D6dff8?nBGfgdR(el}T-E zZ)`6ErlKghyr~!}MlN+M6)OjBLA8(r$5C-|;I>p-8E|82V=4g?dFOj4y)j)5+`F;2 z9Jp^|Upa99#{P2P%*ISPaCT$195}ZzR|ZT+)6sI^el)`n;^xrf>G3kDjU9{~WWZE3 zC6_nVjA|yAx;fQc4&0JzDF<#vwUPt3quR-U>6SE;Ns>r*=o{!8NNO>d%Ay$lmVk38 zhQB4?JStBvlY!JgIdB10AO|j?O60(|Qn$*1Z>MgT1J9%8$${^u?w12UNj)hCeu{cZ z4*U%Dj2!q`>RCDPQfjFjcp0@!4!oRNE(hK~ZIA=+q4vmu_fmW1!276ua^NG>5jpTl z>XaP#0@Wx7ZbCJY0n-NBK)}gjX6sCMmIGfxcO>8xQEKWubzTN+j59JyNt~K)K{F~W zRh&r}6-F`$&?f1Y|?7XAYDf-HOm^?@w>L+Te<_^;Hjvhd%i!?N(B)Nwhu z*=Wv@h0ig2`A6I?^Ot5u?i0oEB$PB-E}x{SNz-KEbCQ0LgAYTsOXc9ehF;@MEavEP zG@D5;7HISgqUGSmPR35M@Jo%C3gE@wb!83)n3C0q4pZa6}8u3^m<=sm3m-GET$>gc`9%FslFYr_7)qK$~j{;R(^}E zPnEY|gsQv+TU6yOSd%Jm!7^2O3!|y>TRe^{zs1A3@>@KrE5F5qyZ0@}_hyg_wmb1X zCnJ{xkrvFCK=Z{mO`vJ9p#y0dbwkt)5%XM&$CBl8S!OaGlq!qMZDXF9xpINSYMX&w zFnJX=>=k0?(1oDP5{)U4p9yC{u(Qbb2zZkKt!F=wq7afo+01`Z18jpZS(|0EHUow* z>ufG-DuFVB{(=7_V=~e*8RI;0wP09M!NvIaql}%Tg6*&5`8KDi zlq_m9v7w|COIN}6*XE26aKZA_-pz?>JR)kdESG?l8A92ia0%EyA(X7jCw?R&F*THu zm3WNd5F(OFVxA-`=1CGU=QRlKK6S=RGK#xEjEnITiI{UWVr3;sQEibz{YYApQA`Ej zVsS1+PGux5$tadIUu?xni^VJmM2U{Ww|^#1TIewiCL3FVpgcPf06=e3A4-46!87iR0dx{R4}AujE@uv zQG**VQxt6k)@;F7(lCMP!fKEdiRuZqY*qE|;Y%DMqpS3l>|V1A|6avou)RDfgJVrQx^3!JPzob7pqXl@E>GBsw7I(v?^) zux?KbjOVyPUZOivWpziYy6)g*NXBkVRhNA)E>|B`WuzKadqEjZRoc@44?aqqmnu>J z;=aQjpk}M2N^BJ|Hfx;8v{Y48m0PMVqbhi+!l{BMwr&D$p1L@yl&6sc)CJI69?X1Y zMqprcRq^wOtSEd0QPtp2la;)*KqXJcc1{aO@_-PsxPcH~30GAH{iyG>kYq3|AQ@Du zcOZhOgd31FDk29WT8hZ3!yV7EvfV71KFABTV#%ryTVls$$ntnb2t1ylmB%we;_-}-csxTZ zk7r2cPriMiARItqv5PhsOpQDaWoqPcz|_d&fEfah19s><4uqCd>H0kS4A*R^)2R9MHw?0$+3mh6PNG>KriPzi_4SKfU1?{0ae_+Jj-J3Oa>Z~Kz2@pc~XhvRnVmjFTY#}Z3=(m}FepKstYhiyAwo`qky?*jm{UIy@wH1bijM%y+skJ4@M7e$fopz?Ik$sKNXv zIWb};%~XDUAW?aNKrvIc5L2PcX0?^t9X2c8=q+Ib(VlE}TW%2B6q5993eW>w@HpOk z3muCC3AjLW;cGrhUG_B}05AJm-=-jmS>83T zZ≪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 From 56dc9597fc73ae8e6b5d32316993dfa8173ab20f Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 27 Aug 2011 11:38:01 -0700 Subject: [PATCH 079/847] Ignore local.properties contains machine-specific information, not useful to save in repo. --- .gitignore | 1 + local.properties | 10 ---------- 2 files changed, 1 insertion(+), 10 deletions(-) delete mode 100644 local.properties diff --git a/.gitignore b/.gitignore index 4ce94ab5f..db8c7038c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ bin/ docs/Market Icon.png gen/ libs/ +local.properties obj/ .classpath .project diff --git a/local.properties b/local.properties deleted file mode 100644 index c98e8c1fa..000000000 --- a/local.properties +++ /dev/null @@ -1,10 +0,0 @@ -# This file is automatically generated by Android Tools. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# -# This file must *NOT* be checked in Version Control Systems, -# as it contains information specific to your local configuration. - -# location of the SDK. This is only used by Ant -# For customization when using a Version Control System, please read the -# header note. -sdk.dir=/Users/jackpal/code/android-sdk-mac_x86 From ff2dcf634d93a1ead6e595fa64204ba9e8acf34c Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Sat, 27 Aug 2011 12:02:31 -0700 Subject: [PATCH 080/847] The fact that Term.java contains substantially all of the classes of the application clashes with Java's model of one class per file, and is inconvenient when refactoring the code to allow for multiple terminal sessions in the activity. This patch splits out all of the classes into separate files, and groups related classes into packages; no actual changes to the code are made. --- src/jackpal/androidterm/EmulatorView.java | 2078 +++++++++ src/jackpal/androidterm/Term.java | 3911 +---------------- src/jackpal/androidterm/TermService.java | 2 + src/jackpal/androidterm/model/Screen.java | 107 + .../androidterm/model/TextRenderer.java | 31 + .../androidterm/session/TerminalEmulator.java | 1236 ++++++ .../androidterm/session/TranscriptScreen.java | 464 ++ src/jackpal/androidterm/util/ByteQueue.java | 120 + .../{ => util}/ServiceForegroundCompat.java | 2 +- 9 files changed, 4041 insertions(+), 3910 deletions(-) create mode 100644 src/jackpal/androidterm/EmulatorView.java create mode 100644 src/jackpal/androidterm/model/Screen.java create mode 100644 src/jackpal/androidterm/model/TextRenderer.java create mode 100644 src/jackpal/androidterm/session/TerminalEmulator.java create mode 100644 src/jackpal/androidterm/session/TranscriptScreen.java create mode 100644 src/jackpal/androidterm/util/ByteQueue.java rename src/jackpal/androidterm/{ => util}/ServiceForegroundCompat.java (99%) diff --git a/src/jackpal/androidterm/EmulatorView.java b/src/jackpal/androidterm/EmulatorView.java new file mode 100644 index 000000000..88a2249be --- /dev/null +++ b/src/jackpal/androidterm/EmulatorView.java @@ -0,0 +1,2078 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jackpal.androidterm; + +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.ColorMatrixColorFilter; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.Rect; +import android.graphics.Typeface; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.text.ClipboardManager; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.GestureDetector; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; +import android.view.inputmethod.BaseInputConnection; +import android.view.inputmethod.CompletionInfo; +import android.view.inputmethod.CorrectionInfo; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.ExtractedText; +import android.view.inputmethod.ExtractedTextRequest; +import android.view.inputmethod.InputConnection; + +import jackpal.androidterm.model.TextRenderer; +import jackpal.androidterm.session.TerminalEmulator; +import jackpal.androidterm.session.TranscriptScreen; +import jackpal.androidterm.util.ByteQueue; + +/** + * A view on a transcript and a terminal emulator. Displays the text of the + * transcript and the current cursor position of the terminal emulator. + */ +public class EmulatorView extends View implements GestureDetector.OnGestureListener { + + private final String TAG = "EmulatorView"; + private final boolean LOG_KEY_EVENTS = Term.DEBUG && false; + + private Term mTerm; + + /** + * We defer some initialization until we have been layed out in the view + * hierarchy. The boolean tracks when we know what our size is. + */ + private boolean mKnownSize; + + private int mVisibleWidth; + private int mVisibleHeight; + private Rect mVisibleRect = new Rect(); + + /** + * Our transcript. Contains the screen and the transcript. + */ + private TranscriptScreen mTranscriptScreen; + + /** + * Number of rows in the transcript. + */ + private static final int TRANSCRIPT_ROWS = 10000; + + /** + * Total width of each character, in pixels + */ + private int mCharacterWidth; + + /** + * Total height of each character, in pixels + */ + private int mCharacterHeight; + + /** + * Used to render text + */ + private TextRenderer mTextRenderer; + + /** + * Text size. Zero means 4 x 8 font. + */ + private int mTextSize; + + private int mCursorStyle; + private int mCursorBlink; + + /** + * Foreground color. + */ + private int mForeground; + + /** + * Background color. + */ + private int mBackground; + + /** + * Used to paint the cursor + */ + private Paint mCursorPaint; + + private Paint mBackgroundPaint; + + private boolean mUseCookedIme; + + /** + * Our terminal emulator. We use this to get the current cursor position. + */ + private TerminalEmulator mEmulator; + + /** + * The number of rows of text to display. + */ + private int mRows; + + /** + * The number of columns of text to display. + */ + private int mColumns; + + /** + * The number of columns that are visible on the display. + */ + + private int mVisibleColumns; + + /** + * The top row of text to display. Ranges from -activeTranscriptRows to 0 + */ + private int mTopRow; + + private int mLeftColumn; + + private FileDescriptor mTermFd; + /** + * Used to receive data from the remote process. + */ + private FileInputStream mTermIn; + + private FileOutputStream mTermOut; + + private ByteQueue mByteQueue; + + /** + * Used to temporarily hold data received from the remote process. Allocated + * once and used permanently to minimize heap thrashing. + */ + private byte[] mReceiveBuffer; + + /** + * Our private message id, which we use to receive new input from the + * remote process. + */ + private static final int UPDATE = 1; + + private static final int SCREEN_CHECK_PERIOD = 1000; + private static final int CURSOR_BLINK_PERIOD = 1000; + + private boolean mCursorVisible = true; + + private boolean mIsSelectingText = false; + + + private float mScaledDensity; + private static final int SELECT_TEXT_OFFSET_Y = -40; + private int mSelXAnchor = -1; + private int mSelYAnchor = -1; + private int mSelX1 = -1; + private int mSelY1 = -1; + private int mSelX2 = -1; + private int mSelY2 = -1; + + /** + * Used to poll if the view has changed size. Wish there was a better way to do this. + */ + private Runnable mCheckSize = new Runnable() { + + public void run() { + updateSize(false); + mHandler.postDelayed(this, SCREEN_CHECK_PERIOD); + } + }; + + private Runnable mBlinkCursor = new Runnable() { + public void run() { + if (mCursorBlink != 0) { + mCursorVisible = ! mCursorVisible; + mHandler.postDelayed(this, CURSOR_BLINK_PERIOD); + } else { + mCursorVisible = true; + } + // Perhaps just invalidate the character with the cursor. + invalidate(); + } + }; + + /** + * Thread that polls for input from the remote process + */ + + private Thread mPollingThread; + + private GestureDetector mGestureDetector; + private float mScrollRemainder; + private TermKeyListener mKeyListener; + + private String mImeBuffer = ""; + + /** + * Our message handler class. Implements a periodic callback. + */ + private final Handler mHandler = new Handler() { + /** + * Handle the callback message. Call our enclosing class's update + * method. + * + * @param msg The callback message. + */ + @Override + public void handleMessage(Message msg) { + if (msg.what == UPDATE) { + update(); + } + } + }; + + public EmulatorView(Context context) { + super(context); + commonConstructor(); + } + + public void setScaledDensity(float scaledDensity) { + mScaledDensity = scaledDensity; + } + + public void onResume() { + updateSize(false); + mHandler.postDelayed(mCheckSize, SCREEN_CHECK_PERIOD); + if (mCursorBlink != 0) { + mHandler.postDelayed(mBlinkCursor, CURSOR_BLINK_PERIOD); + } + } + + public void onPause() { + mHandler.removeCallbacks(mCheckSize); + if (mCursorBlink != 0) { + mHandler.removeCallbacks(mBlinkCursor); + } + } + + public void register(Term term, TermKeyListener listener) { + mTerm = term; + mKeyListener = listener; + } + + public void setColors(int foreground, int background) { + mForeground = foreground; + mBackground = background; + updateText(); + } + + public String getTranscriptText() { + return mEmulator.getTranscriptText(); + } + + public void resetTerminal() { + mEmulator.reset(); + invalidate(); + } + + @Override + public boolean onCheckIsTextEditor() { + return true; + } + + @Override + public InputConnection onCreateInputConnection(EditorInfo outAttrs) { + outAttrs.inputType = mUseCookedIme ? + EditorInfo.TYPE_CLASS_TEXT : + EditorInfo.TYPE_NULL; + return new InputConnection() { + private boolean mInBatchEdit; + /** + * Used to handle composing text requests + */ + private int mCursor; + private int mComposingTextStart; + private int mComposingTextEnd; + private int mSelectedTextStart; + private int mSelectedTextEnd; + + private void sendChar(int c) { + try { + mapAndSend(c); + } catch (IOException ex) { + + } + } + + private void sendText(CharSequence text) { + int n = text.length(); + try { + for(int i = 0; i < n; i++) { + char c = text.charAt(i); + mapAndSend(c); + } + mTermOut.flush(); + } catch (IOException e) { + Log.e(TAG, "error writing ", e); + } + } + + private void mapAndSend(int c) throws IOException { + int result = mKeyListener.mapControlChar(c); + if (result < TermKeyListener.KEYCODE_OFFSET) { + mTermOut.write(result); + } else { + mKeyListener.handleKeyCode(result - TermKeyListener.KEYCODE_OFFSET, mTermOut, getKeypadApplicationMode()); + } + } + + public boolean beginBatchEdit() { + if (Term.LOG_IME) { + Log.w(TAG, "beginBatchEdit"); + } + setImeBuffer(""); + mCursor = 0; + mComposingTextStart = 0; + mComposingTextEnd = 0; + mInBatchEdit = true; + return true; + } + + public boolean clearMetaKeyStates(int arg0) { + if (Term.LOG_IME) { + Log.w(TAG, "clearMetaKeyStates " + arg0); + } + return false; + } + + public boolean commitCompletion(CompletionInfo arg0) { + if (Term.LOG_IME) { + Log.w(TAG, "commitCompletion " + arg0); + } + return false; + } + + public boolean endBatchEdit() { + if (Term.LOG_IME) { + Log.w(TAG, "endBatchEdit"); + } + mInBatchEdit = false; + return true; + } + + public boolean finishComposingText() { + if (Term.LOG_IME) { + Log.w(TAG, "finishComposingText"); + } + sendText(mImeBuffer); + setImeBuffer(""); + mComposingTextStart = 0; + mComposingTextEnd = 0; + mCursor = 0; + return true; + } + + public int getCursorCapsMode(int arg0) { + if (Term.LOG_IME) { + Log.w(TAG, "getCursorCapsMode(" + arg0 + ")"); + } + return 0; + } + + public ExtractedText getExtractedText(ExtractedTextRequest arg0, + int arg1) { + if (Term.LOG_IME) { + Log.w(TAG, "getExtractedText" + arg0 + "," + arg1); + } + return null; + } + + public CharSequence getTextAfterCursor(int n, int flags) { + if (Term.LOG_IME) { + Log.w(TAG, "getTextAfterCursor(" + n + "," + flags + ")"); + } + int len = Math.min(n, mImeBuffer.length() - mCursor); + if (len <= 0 || mCursor < 0 || mCursor >= mImeBuffer.length()) { + return ""; + } + return mImeBuffer.substring(mCursor, mCursor + len); + } + + public CharSequence getTextBeforeCursor(int n, int flags) { + if (Term.LOG_IME) { + Log.w(TAG, "getTextBeforeCursor(" + n + "," + flags + ")"); + } + int len = Math.min(n, mCursor); + if (len <= 0 || mCursor < 0 || mCursor >= mImeBuffer.length()) { + return ""; + } + return mImeBuffer.substring(mCursor-len, mCursor); + } + + public boolean performContextMenuAction(int arg0) { + if (Term.LOG_IME) { + Log.w(TAG, "performContextMenuAction" + arg0); + } + return true; + } + + public boolean performPrivateCommand(String arg0, Bundle arg1) { + if (Term.LOG_IME) { + Log.w(TAG, "performPrivateCommand" + arg0 + "," + arg1); + } + return true; + } + + public boolean reportFullscreenMode(boolean arg0) { + if (Term.LOG_IME) { + Log.w(TAG, "reportFullscreenMode" + arg0); + } + return true; + } + + public boolean commitCorrection (CorrectionInfo correctionInfo) { + if (Term.LOG_IME) { + Log.w(TAG, "commitCorrection"); + } + return true; + } + + public boolean commitText(CharSequence text, int newCursorPosition) { + if (Term.LOG_IME) { + Log.w(TAG, "commitText(\"" + text + "\", " + newCursorPosition + ")"); + } + clearComposingText(); + sendText(text); + setImeBuffer(""); + mCursor = 0; + return true; + } + + private void clearComposingText() { + setImeBuffer(mImeBuffer.substring(0, mComposingTextStart) + + mImeBuffer.substring(mComposingTextEnd)); + if (mCursor < mComposingTextStart) { + // do nothing + } else if (mCursor < mComposingTextEnd) { + mCursor = mComposingTextStart; + } else { + mCursor -= mComposingTextEnd - mComposingTextStart; + } + mComposingTextEnd = mComposingTextStart = 0; + } + + public boolean deleteSurroundingText(int leftLength, int rightLength) { + if (Term.LOG_IME) { + Log.w(TAG, "deleteSurroundingText(" + leftLength + + "," + rightLength + ")"); + } + if (leftLength > 0) { + for (int i = 0; i < leftLength; i++) { + sendKeyEvent( + new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)); + } + } else if ((leftLength == 0) && (rightLength == 0)) { + // Delete key held down / repeating + sendKeyEvent( + new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)); + } + // TODO: handle forward deletes. + return true; + } + + public boolean performEditorAction(int actionCode) { + if (Term.LOG_IME) { + Log.w(TAG, "performEditorAction(" + actionCode + ")"); + } + if (actionCode == EditorInfo.IME_ACTION_UNSPECIFIED) { + // The "return" key has been pressed on the IME. + sendText("\n"); + } + return true; + } + + public boolean sendKeyEvent(KeyEvent event) { + if (Term.LOG_IME) { + Log.w(TAG, "sendKeyEvent(" + event + ")"); + } + // Some keys are sent here rather than to commitText. + // In particular, del and the digit keys are sent here. + // (And I have reports that the HTC Magic also sends Return here.) + // As a bit of defensive programming, handle every key. + dispatchKeyEvent(event); + return true; + } + + public boolean setComposingText(CharSequence text, int newCursorPosition) { + if (Term.LOG_IME) { + Log.w(TAG, "setComposingText(\"" + text + "\", " + newCursorPosition + ")"); + } + setImeBuffer(mImeBuffer.substring(0, mComposingTextStart) + + text + mImeBuffer.substring(mComposingTextEnd)); + mComposingTextEnd = mComposingTextStart + text.length(); + mCursor = newCursorPosition > 0 ? mComposingTextEnd + newCursorPosition - 1 + : mComposingTextStart - newCursorPosition; + return true; + } + + public boolean setSelection(int start, int end) { + if (Term.LOG_IME) { + Log.w(TAG, "setSelection" + start + "," + end); + } + int length = mImeBuffer.length(); + if (start == end && start > 0 && start < length) { + mSelectedTextStart = mSelectedTextEnd = 0; + mCursor = start; + } else if (start < end && start > 0 && end < length) { + mSelectedTextStart = start; + mSelectedTextEnd = end; + mCursor = start; + } + return true; + } + + public boolean setComposingRegion(int start, int end) { + if (Term.LOG_IME) { + Log.w(TAG, "setComposingRegion " + start + "," + end); + } + if (start < end && start > 0 && end < mImeBuffer.length()) { + clearComposingText(); + mComposingTextStart = start; + mComposingTextEnd = end; + } + return true; + } + + public CharSequence getSelectedText(int flags) { + if (Term.LOG_IME) { + Log.w(TAG, "getSelectedText " + flags); + } + return mImeBuffer.substring(mSelectedTextStart, mSelectedTextEnd+1); + } + + }; + } + + private void setImeBuffer(String buffer) { + if (!buffer.equals(mImeBuffer)) { + invalidate(); + } + mImeBuffer = buffer; + } + + public boolean getKeypadApplicationMode() { + return mEmulator.getKeypadApplicationMode(); + } + + public EmulatorView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public EmulatorView(Context context, AttributeSet attrs, + int defStyle) { + super(context, attrs, defStyle); + // TypedArray a = + // context.obtainStyledAttributes(android.R.styleable.View); + // initializeScrollbars(a); + // a.recycle(); + commonConstructor(); + } + + private void commonConstructor() { + mTextRenderer = null; + mCursorPaint = new Paint(); + mCursorPaint.setARGB(255,128,128,128); + mBackgroundPaint = new Paint(); + mTopRow = 0; + mLeftColumn = 0; + mGestureDetector = new GestureDetector(this); + // mGestureDetector.setIsLongpressEnabled(false); + setVerticalScrollBarEnabled(true); + } + + @Override + protected int computeVerticalScrollRange() { + return mTranscriptScreen.getActiveRows(); + } + + @Override + protected int computeVerticalScrollExtent() { + return mRows; + } + + @Override + protected int computeVerticalScrollOffset() { + return mTranscriptScreen.getActiveRows() + mTopRow - mRows; + } + + /** + * Call this to initialize the view. + * + * @param termFd the file descriptor + * @param termOut the output stream for the pseudo-teletype + */ + public void initialize(FileDescriptor termFd, FileOutputStream termOut) { + mTermOut = termOut; + mTermFd = termFd; + mTextSize = 10; + mForeground = Term.WHITE; + mBackground = Term.BLACK; + updateText(); + mTermIn = new FileInputStream(mTermFd); + mReceiveBuffer = new byte[4 * 1024]; + mByteQueue = new ByteQueue(4 * 1024); + } + + /** + * Accept a sequence of bytes (typically from the pseudo-tty) and process + * them. + * + * @param buffer a byte array containing bytes to be processed + * @param base the index of the first byte in the buffer to process + * @param length the number of bytes to process + */ + public void append(byte[] buffer, int base, int length) { + mEmulator.append(buffer, base, length); + if ( mIsSelectingText ) { + int rowShift = mEmulator.getScrollCounter(); + mSelY1 -= rowShift; + mSelY2 -= rowShift; + mSelYAnchor -= rowShift; + } + mEmulator.clearScrollCounter(); + ensureCursorVisible(); + invalidate(); + } + + /** + * Page the terminal view (scroll it up or down by delta screenfulls.) + * + * @param delta the number of screens to scroll. Positive means scroll down, + * negative means scroll up. + */ + public void page(int delta) { + mTopRow = + Math.min(0, Math.max(-(mTranscriptScreen + .getActiveTranscriptRows()), mTopRow + mRows * delta)); + invalidate(); + } + + /** + * Page the terminal view horizontally. + * + * @param deltaColumns the number of columns to scroll. Positive scrolls to + * the right. + */ + public void pageHorizontal(int deltaColumns) { + mLeftColumn = + Math.max(0, Math.min(mLeftColumn + deltaColumns, mColumns + - mVisibleColumns)); + invalidate(); + } + + /** + * Sets the text size, which in turn sets the number of rows and columns + * + * @param fontSize the new font size, in pixels. + */ + public void setTextSize(int fontSize) { + mTextSize = fontSize; + updateText(); + } + + public void setCursorStyle(int style, int blink) { + mCursorStyle = style; + if (blink != 0 && mCursorBlink == 0) { + mHandler.postDelayed(mBlinkCursor, CURSOR_BLINK_PERIOD); + } else if (blink == 0 && mCursorBlink != 0) { + mHandler.removeCallbacks(mBlinkCursor); + } + mCursorBlink = blink; + } + + public void setUseCookedIME(boolean useCookedIME) { + mUseCookedIme = useCookedIME; + } + + // Begin GestureDetector.OnGestureListener methods + + public boolean onSingleTapUp(MotionEvent e) { + return true; + } + + public void onLongPress(MotionEvent e) { + showContextMenu(); + } + + public boolean onScroll(MotionEvent e1, MotionEvent e2, + float distanceX, float distanceY) { + distanceY += mScrollRemainder; + int deltaRows = (int) (distanceY / mCharacterHeight); + mScrollRemainder = distanceY - deltaRows * mCharacterHeight; + mTopRow = + Math.min(0, Math.max(-(mTranscriptScreen + .getActiveTranscriptRows()), mTopRow + deltaRows)); + invalidate(); + + return true; + } + + public void onSingleTapConfirmed(MotionEvent e) { + } + + public boolean onJumpTapDown(MotionEvent e1, MotionEvent e2) { + // Scroll to bottom + mTopRow = 0; + invalidate(); + return true; + } + + public boolean onJumpTapUp(MotionEvent e1, MotionEvent e2) { + // Scroll to top + mTopRow = -mTranscriptScreen.getActiveTranscriptRows(); + invalidate(); + return true; + } + + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, + float velocityY) { + // TODO: add animation man's (non animated) fling + mScrollRemainder = 0.0f; + onScroll(e1, e2, 2 * velocityX, -2 * velocityY); + return true; + } + + public void onShowPress(MotionEvent e) { + } + + public boolean onDown(MotionEvent e) { + mScrollRemainder = 0.0f; + return true; + } + + // End GestureDetector.OnGestureListener methods + + @Override public boolean onTouchEvent(MotionEvent ev) { + if (mIsSelectingText) { + return onTouchEventWhileSelectingText(ev); + } else { + return mGestureDetector.onTouchEvent(ev); + } + } + + private boolean onTouchEventWhileSelectingText(MotionEvent ev) { + int action = ev.getAction(); + int cx = (int)(ev.getX() / mCharacterWidth); + int cy = Math.max(0, + (int)((ev.getY() + SELECT_TEXT_OFFSET_Y * mScaledDensity) + / mCharacterHeight) + mTopRow); + switch (action) { + case MotionEvent.ACTION_DOWN: + mSelXAnchor = cx; + mSelYAnchor = cy; + mSelX1 = cx; + mSelY1 = cy; + mSelX2 = mSelX1; + mSelY2 = mSelY1; + break; + case MotionEvent.ACTION_MOVE: + case MotionEvent.ACTION_UP: + int minx = Math.min(mSelXAnchor, cx); + int maxx = Math.max(mSelXAnchor, cx); + int miny = Math.min(mSelYAnchor, cy); + int maxy = Math.max(mSelYAnchor, cy); + mSelX1 = minx; + mSelY1 = miny; + mSelX2 = maxx; + mSelY2 = maxy; + if (action == MotionEvent.ACTION_UP) { + ClipboardManager clip = (ClipboardManager) + getContext().getApplicationContext() + .getSystemService(Context.CLIPBOARD_SERVICE); + clip.setText(getSelectedText().trim()); + toggleSelectingText(); + } + invalidate(); + break; + default: + toggleSelectingText(); + invalidate(); + break; + } + return true; + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (LOG_KEY_EVENTS) { + Log.w(TAG, "onKeyDown " + keyCode); + } + if (handleControlKey(keyCode, true)) { + return true; + } else if (handleFnKey(keyCode, true)) { + return true; + } else if (isSystemKey(keyCode, event)) { + // Don't intercept the system keys + return super.onKeyDown(keyCode, event); + } + + // Translate the keyCode into an ASCII character. + + try { + mKeyListener.keyDown(keyCode, event, mTermOut, + getKeypadApplicationMode()); + } catch (IOException e) { + // Ignore I/O exceptions + } + return true; + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + if (LOG_KEY_EVENTS) { + Log.w(TAG, "onKeyUp " + keyCode); + } + if (handleControlKey(keyCode, false)) { + return true; + } else if (handleFnKey(keyCode, false)) { + return true; + } else if (isSystemKey(keyCode, event)) { + // Don't intercept the system keys + return super.onKeyUp(keyCode, event); + } + + mKeyListener.keyUp(keyCode); + return true; + } + + + private boolean handleControlKey(int keyCode, boolean down) { + if (keyCode == mTerm.getControlKeyCode()) { + if (LOG_KEY_EVENTS) { + Log.w(TAG, "handleControlKey " + keyCode); + } + mKeyListener.handleControlKey(down); + return true; + } + return false; + } + + private boolean handleFnKey(int keyCode, boolean down) { + if (keyCode == mTerm.getFnKeyCode()) { + if (LOG_KEY_EVENTS) { + Log.w(TAG, "handleFnKey " + keyCode); + } + mKeyListener.handleFnKey(down); + return true; + } + return false; + } + + private boolean isSystemKey(int keyCode, KeyEvent event) { + return event.isSystem(); + } + + private void updateText() { + if (mTextSize > 0) { + mTextRenderer = new PaintRenderer(mTextSize, mForeground, + mBackground); + } + else { + mTextRenderer = new Bitmap4x8FontRenderer(getResources(), + mForeground, mBackground); + } + mBackgroundPaint.setColor(mBackground); + mCharacterWidth = mTextRenderer.getCharacterWidth(); + mCharacterHeight = mTextRenderer.getCharacterHeight(); + + updateSize(true); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + boolean oldKnownSize = mKnownSize; + if (!mKnownSize) { + mKnownSize = true; + } + updateSize(false); + if (!oldKnownSize) { + // Set up a thread to read input from the + // pseudo-teletype: + + mPollingThread = new Thread(new Runnable() { + + public void run() { + try { + while(true) { + int read = mTermIn.read(mBuffer); + mByteQueue.write(mBuffer, 0, read); + mHandler.sendMessage( + mHandler.obtainMessage(UPDATE)); + } + } catch (IOException e) { + } catch (InterruptedException e) { + } + } + private byte[] mBuffer = new byte[4096]; + }); + mPollingThread.setName("Input reader"); + mPollingThread.start(); + } + } + + private void updateSize(int w, int h) { + mColumns = Math.max(1, w / mCharacterWidth); + mRows = Math.max(1, h / mCharacterHeight); + mVisibleColumns = mVisibleWidth / mCharacterWidth; + + // Inform the attached pty of our new size: + Exec.setPtyWindowSize(mTermFd, mRows, mColumns, w, h); + + + if (mTranscriptScreen != null) { + mEmulator.updateSize(mColumns, mRows); + } else { + mTranscriptScreen = + new TranscriptScreen(mColumns, TRANSCRIPT_ROWS, mRows, 0, 7); + mEmulator = + new TerminalEmulator(mTranscriptScreen, mColumns, mRows, + mTermOut); + } + + // Reset our paging: + mTopRow = 0; + mLeftColumn = 0; + + invalidate(); + } + + void updateSize(boolean force) { + if (mKnownSize) { + getWindowVisibleDisplayFrame(mVisibleRect); + int w = mVisibleRect.width(); + int h = mVisibleRect.height(); + // Log.w("Term", "(" + w + ", " + h + ")"); + if (force || w != mVisibleWidth || h != mVisibleHeight) { + mVisibleWidth = w; + mVisibleHeight = h; + updateSize(mVisibleWidth, mVisibleHeight); + } + } + } + + /** + * Look for new input from the ptty, send it to the terminal emulator. + */ + private void update() { + int bytesAvailable = mByteQueue.getBytesAvailable(); + int bytesToRead = Math.min(bytesAvailable, mReceiveBuffer.length); + try { + int bytesRead = mByteQueue.read(mReceiveBuffer, 0, bytesToRead); + append(mReceiveBuffer, 0, bytesRead); + } catch (InterruptedException e) { + } + } + + @Override + protected void onDraw(Canvas canvas) { + updateSize(false); + int w = getWidth(); + int h = getHeight(); + canvas.drawRect(0, 0, w, h, mBackgroundPaint); + float x = -mLeftColumn * mCharacterWidth; + float y = mCharacterHeight; + int endLine = mTopRow + mRows; + int cx = mEmulator.getCursorCol(); + int cy = mEmulator.getCursorRow(); + for (int i = mTopRow; i < endLine; i++) { + int cursorX = -1; + if (i == cy && mCursorVisible) { + cursorX = cx; + } + int selx1 = -1; + int selx2 = -1; + if ( i >= mSelY1 && i <= mSelY2 ) { + if ( i == mSelY1 ) { + selx1 = mSelX1; + } + if ( i == mSelY2 ) { + selx2 = mSelX2; + } else { + selx2 = mColumns; + } + } + mTranscriptScreen.drawText(i, canvas, x, y, mTextRenderer, cursorX, selx1, selx2, mImeBuffer); + y += mCharacterHeight; + } + } + + private void ensureCursorVisible() { + mTopRow = 0; + if (mVisibleColumns > 0) { + int cx = mEmulator.getCursorCol(); + int visibleCursorX = mEmulator.getCursorCol() - mLeftColumn; + if (visibleCursorX < 0) { + mLeftColumn = cx; + } else if (visibleCursorX >= mVisibleColumns) { + mLeftColumn = (cx - mVisibleColumns) + 1; + } + } + } + + public void toggleSelectingText() { + mIsSelectingText = ! mIsSelectingText; + setVerticalScrollBarEnabled( ! mIsSelectingText ); + if ( ! mIsSelectingText ) { + mSelX1 = -1; + mSelY1 = -1; + mSelX2 = -1; + mSelY2 = -1; + } + } + + public boolean getSelectingText() { + return mIsSelectingText; + } + + public String getSelectedText() { + return mEmulator.getSelectedText(mSelX1, mSelY1, mSelX2, mSelY2); + } +} + +abstract class BaseTextRenderer implements TextRenderer { + protected int[] mForePaint = { + 0xff000000, // Black + 0xffff0000, // Red + 0xff00ff00, // green + 0xffffff00, // yellow + 0xff0000ff, // blue + 0xffff00ff, // magenta + 0xff00ffff, // cyan + 0xffffffff // white -- is overridden by constructor + }; + protected int[] mBackPaint = { + 0xff000000, // Black -- is overridden by constructor + 0xffcc0000, // Red + 0xff00cc00, // green + 0xffcccc00, // yellow + 0xff0000cc, // blue + 0xffff00cc, // magenta + 0xff00cccc, // cyan + 0xffffffff // white + }; + protected final static int mCursorPaint = 0xff808080; + + public BaseTextRenderer(int forePaintColor, int backPaintColor) { + mForePaint[7] = forePaintColor; + mBackPaint[0] = backPaintColor; + + } +} + +class Bitmap4x8FontRenderer extends BaseTextRenderer { + private final static int kCharacterWidth = 4; + private final static int kCharacterHeight = 8; + private Bitmap mFont; + private int mCurrentForeColor; + private int mCurrentBackColor; + private float[] mColorMatrix; + private Paint mPaint; + private static final float BYTE_SCALE = 1.0f / 255.0f; + + public Bitmap4x8FontRenderer(Resources resources, + int forePaintColor, int backPaintColor) { + super(forePaintColor, backPaintColor); + mFont = BitmapFactory.decodeResource(resources, + R.drawable.atari_small); + mPaint = new Paint(); + mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); + } + + public int getCharacterWidth() { + return kCharacterWidth; + } + + public int getCharacterHeight() { + return kCharacterHeight; + } + + public void drawTextRun(Canvas canvas, float x, float y, + int lineOffset, char[] text, int index, int count, + boolean cursor, int foreColor, int backColor) { + setColorMatrix(mForePaint[foreColor & 7], + cursor ? mCursorPaint : mBackPaint[backColor & 7]); + int destX = (int) x + kCharacterWidth * lineOffset; + int destY = (int) y; + Rect srcRect = new Rect(); + Rect destRect = new Rect(); + destRect.top = (destY - kCharacterHeight); + destRect.bottom = destY; + for(int i = 0; i < count; i++) { + char c = text[i + index]; + if ((cursor || (c != 32)) && (c < 128)) { + int cellX = c & 31; + int cellY = (c >> 5) & 3; + int srcX = cellX * kCharacterWidth; + int srcY = cellY * kCharacterHeight; + srcRect.set(srcX, srcY, + srcX + kCharacterWidth, srcY + kCharacterHeight); + destRect.left = destX; + destRect.right = destX + kCharacterWidth; + canvas.drawBitmap(mFont, srcRect, destRect, mPaint); + } + destX += kCharacterWidth; + } + } + + private void setColorMatrix(int foreColor, int backColor) { + if ((foreColor != mCurrentForeColor) + || (backColor != mCurrentBackColor) + || (mColorMatrix == null)) { + mCurrentForeColor = foreColor; + mCurrentBackColor = backColor; + if (mColorMatrix == null) { + mColorMatrix = new float[20]; + mColorMatrix[18] = 1.0f; // Just copy Alpha + } + for (int component = 0; component < 3; component++) { + int rightShift = (2 - component) << 3; + int fore = 0xff & (foreColor >> rightShift); + int back = 0xff & (backColor >> rightShift); + int delta = back - fore; + mColorMatrix[component * 6] = delta * BYTE_SCALE; + mColorMatrix[component * 5 + 4] = fore; + } + mPaint.setColorFilter(new ColorMatrixColorFilter(mColorMatrix)); + } + } +} + +class PaintRenderer extends BaseTextRenderer { + public PaintRenderer(int fontSize, int forePaintColor, int backPaintColor) { + super(forePaintColor, backPaintColor); + mTextPaint = new Paint(); + mTextPaint.setTypeface(Typeface.MONOSPACE); + mTextPaint.setAntiAlias(true); + mTextPaint.setTextSize(fontSize); + + mCharHeight = (int) Math.ceil(mTextPaint.getFontSpacing()); + mCharAscent = (int) Math.ceil(mTextPaint.ascent()); + mCharDescent = mCharHeight + mCharAscent; + mCharWidth = (int) mTextPaint.measureText(EXAMPLE_CHAR, 0, 1); + } + + public void drawTextRun(Canvas canvas, float x, float y, int lineOffset, + char[] text, int index, int count, + boolean cursor, int foreColor, int backColor) { + if (cursor) { + mTextPaint.setColor(mCursorPaint); + } else { + mTextPaint.setColor(mBackPaint[backColor & 0x7]); + } + float left = x + lineOffset * mCharWidth; + canvas.drawRect(left, y + mCharAscent, + left + count * mCharWidth, y + mCharDescent, + mTextPaint); + boolean bold = ( foreColor & 0x8 ) != 0; + boolean underline = (backColor & 0x8) != 0; + if (bold) { + mTextPaint.setFakeBoldText(true); + } + if (underline) { + mTextPaint.setUnderlineText(true); + } + mTextPaint.setColor(mForePaint[foreColor & 0x7]); + canvas.drawText(text, index, count, left, y, mTextPaint); + if (bold) { + mTextPaint.setFakeBoldText(false); + } + if (underline) { + mTextPaint.setUnderlineText(false); + } + } + + public int getCharacterHeight() { + return mCharHeight; + } + + public int getCharacterWidth() { + return mCharWidth; + } + + + private Paint mTextPaint; + private int mCharWidth; + private int mCharHeight; + private int mCharAscent; + private int mCharDescent; + private static final char[] EXAMPLE_CHAR = {'X'}; + } + +/** + * An ASCII key listener. Supports control characters and escape. Keeps track of + * the current state of the alt, shift, and control keys. + */ +class TermKeyListener { + /** + * Android key codes that are defined in the Android 2.3 API. + * We want to recognize these codes, because they will be sent to our + * app when we run on Android 2.3 systems. + * But we don't want to accidentally use 2.3-specific APIs. + * So we compile against the Android 1.6 APIs, and have a copy of the codes here. + */ + + /** Key code constant: Unknown key code. */ + public static final int KEYCODE_UNKNOWN = 0; + /** Key code constant: Soft Left key. + * Usually situated below the display on phones and used as a multi-function + * feature key for selecting a software defined function shown on the bottom left + * of the display. */ + public static final int KEYCODE_SOFT_LEFT = 1; + /** Key code constant: Soft Right key. + * Usually situated below the display on phones and used as a multi-function + * feature key for selecting a software defined function shown on the bottom right + * of the display. */ + public static final int KEYCODE_SOFT_RIGHT = 2; + /** Key code constant: Home key. + * This key is handled by the framework and is never delivered to applications. */ + public static final int KEYCODE_HOME = 3; + /** Key code constant: Back key. */ + public static final int KEYCODE_BACK = 4; + /** Key code constant: Call key. */ + public static final int KEYCODE_CALL = 5; + /** Key code constant: End Call key. */ + public static final int KEYCODE_ENDCALL = 6; + /** Key code constant: '0' key. */ + public static final int KEYCODE_0 = 7; + /** Key code constant: '1' key. */ + public static final int KEYCODE_1 = 8; + /** Key code constant: '2' key. */ + public static final int KEYCODE_2 = 9; + /** Key code constant: '3' key. */ + public static final int KEYCODE_3 = 10; + /** Key code constant: '4' key. */ + public static final int KEYCODE_4 = 11; + /** Key code constant: '5' key. */ + public static final int KEYCODE_5 = 12; + /** Key code constant: '6' key. */ + public static final int KEYCODE_6 = 13; + /** Key code constant: '7' key. */ + public static final int KEYCODE_7 = 14; + /** Key code constant: '8' key. */ + public static final int KEYCODE_8 = 15; + /** Key code constant: '9' key. */ + public static final int KEYCODE_9 = 16; + /** Key code constant: '*' key. */ + public static final int KEYCODE_STAR = 17; + /** Key code constant: '#' key. */ + public static final int KEYCODE_POUND = 18; + /** Key code constant: Directional Pad Up key. + * May also be synthesized from trackball motions. */ + public static final int KEYCODE_DPAD_UP = 19; + /** Key code constant: Directional Pad Down key. + * May also be synthesized from trackball motions. */ + public static final int KEYCODE_DPAD_DOWN = 20; + /** Key code constant: Directional Pad Left key. + * May also be synthesized from trackball motions. */ + public static final int KEYCODE_DPAD_LEFT = 21; + /** Key code constant: Directional Pad Right key. + * May also be synthesized from trackball motions. */ + public static final int KEYCODE_DPAD_RIGHT = 22; + /** Key code constant: Directional Pad Center key. + * May also be synthesized from trackball motions. */ + public static final int KEYCODE_DPAD_CENTER = 23; + /** Key code constant: Volume Up key. + * Adjusts the speaker volume up. */ + public static final int KEYCODE_VOLUME_UP = 24; + /** Key code constant: Volume Down key. + * Adjusts the speaker volume down. */ + public static final int KEYCODE_VOLUME_DOWN = 25; + /** Key code constant: Power key. */ + public static final int KEYCODE_POWER = 26; + /** Key code constant: Camera key. + * Used to launch a camera application or take pictures. */ + public static final int KEYCODE_CAMERA = 27; + /** Key code constant: Clear key. */ + public static final int KEYCODE_CLEAR = 28; + /** Key code constant: 'A' key. */ + public static final int KEYCODE_A = 29; + /** Key code constant: 'B' key. */ + public static final int KEYCODE_B = 30; + /** Key code constant: 'C' key. */ + public static final int KEYCODE_C = 31; + /** Key code constant: 'D' key. */ + public static final int KEYCODE_D = 32; + /** Key code constant: 'E' key. */ + public static final int KEYCODE_E = 33; + /** Key code constant: 'F' key. */ + public static final int KEYCODE_F = 34; + /** Key code constant: 'G' key. */ + public static final int KEYCODE_G = 35; + /** Key code constant: 'H' key. */ + public static final int KEYCODE_H = 36; + /** Key code constant: 'I' key. */ + public static final int KEYCODE_I = 37; + /** Key code constant: 'J' key. */ + public static final int KEYCODE_J = 38; + /** Key code constant: 'K' key. */ + public static final int KEYCODE_K = 39; + /** Key code constant: 'L' key. */ + public static final int KEYCODE_L = 40; + /** Key code constant: 'M' key. */ + public static final int KEYCODE_M = 41; + /** Key code constant: 'N' key. */ + public static final int KEYCODE_N = 42; + /** Key code constant: 'O' key. */ + public static final int KEYCODE_O = 43; + /** Key code constant: 'P' key. */ + public static final int KEYCODE_P = 44; + /** Key code constant: 'Q' key. */ + public static final int KEYCODE_Q = 45; + /** Key code constant: 'R' key. */ + public static final int KEYCODE_R = 46; + /** Key code constant: 'S' key. */ + public static final int KEYCODE_S = 47; + /** Key code constant: 'T' key. */ + public static final int KEYCODE_T = 48; + /** Key code constant: 'U' key. */ + public static final int KEYCODE_U = 49; + /** Key code constant: 'V' key. */ + public static final int KEYCODE_V = 50; + /** Key code constant: 'W' key. */ + public static final int KEYCODE_W = 51; + /** Key code constant: 'X' key. */ + public static final int KEYCODE_X = 52; + /** Key code constant: 'Y' key. */ + public static final int KEYCODE_Y = 53; + /** Key code constant: 'Z' key. */ + public static final int KEYCODE_Z = 54; + /** Key code constant: ',' key. */ + public static final int KEYCODE_COMMA = 55; + /** Key code constant: '.' key. */ + public static final int KEYCODE_PERIOD = 56; + /** Key code constant: Left Alt modifier key. */ + public static final int KEYCODE_ALT_LEFT = 57; + /** Key code constant: Right Alt modifier key. */ + public static final int KEYCODE_ALT_RIGHT = 58; + /** Key code constant: Left Shift modifier key. */ + public static final int KEYCODE_SHIFT_LEFT = 59; + /** Key code constant: Right Shift modifier key. */ + public static final int KEYCODE_SHIFT_RIGHT = 60; + /** Key code constant: Tab key. */ + public static final int KEYCODE_TAB = 61; + /** Key code constant: Space key. */ + public static final int KEYCODE_SPACE = 62; + /** Key code constant: Symbol modifier key. + * Used to enter alternate symbols. */ + public static final int KEYCODE_SYM = 63; + /** Key code constant: Explorer special function key. + * Used to launch a browser application. */ + public static final int KEYCODE_EXPLORER = 64; + /** Key code constant: Envelope special function key. + * Used to launch a mail application. */ + public static final int KEYCODE_ENVELOPE = 65; + /** Key code constant: Enter key. */ + public static final int KEYCODE_ENTER = 66; + /** Key code constant: Backspace key. + * Deletes characters before the insertion point, unlike {@link #KEYCODE_FORWARD_DEL}. */ + public static final int KEYCODE_DEL = 67; + /** Key code constant: '`' (backtick) key. */ + public static final int KEYCODE_GRAVE = 68; + /** Key code constant: '-'. */ + public static final int KEYCODE_MINUS = 69; + /** Key code constant: '=' key. */ + public static final int KEYCODE_EQUALS = 70; + /** Key code constant: '[' key. */ + public static final int KEYCODE_LEFT_BRACKET = 71; + /** Key code constant: ']' key. */ + public static final int KEYCODE_RIGHT_BRACKET = 72; + /** Key code constant: '\' key. */ + public static final int KEYCODE_BACKSLASH = 73; + /** Key code constant: ';' key. */ + public static final int KEYCODE_SEMICOLON = 74; + /** Key code constant: ''' (apostrophe) key. */ + public static final int KEYCODE_APOSTROPHE = 75; + /** Key code constant: '/' key. */ + public static final int KEYCODE_SLASH = 76; + /** Key code constant: '@' key. */ + public static final int KEYCODE_AT = 77; + /** Key code constant: Number modifier key. + * Used to enter numeric symbols. + * This key is not Num Lock; it is more like {@link #KEYCODE_ALT_LEFT} and is + * interpreted as an ALT key by {@link android.text.method.MetaKeyKeyListener}. */ + public static final int KEYCODE_NUM = 78; + /** Key code constant: Headset Hook key. + * Used to hang up calls and stop media. */ + public static final int KEYCODE_HEADSETHOOK = 79; + /** Key code constant: Camera Focus key. + * Used to focus the camera. */ + public static final int KEYCODE_FOCUS = 80; // *Camera* focus + /** Key code constant: '+' key. */ + public static final int KEYCODE_PLUS = 81; + /** Key code constant: Menu key. */ + public static final int KEYCODE_MENU = 82; + /** Key code constant: Notification key. */ + public static final int KEYCODE_NOTIFICATION = 83; + /** Key code constant: Search key. */ + public static final int KEYCODE_SEARCH = 84; + /** Key code constant: Play/Pause media key. */ + public static final int KEYCODE_MEDIA_PLAY_PAUSE= 85; + /** Key code constant: Stop media key. */ + public static final int KEYCODE_MEDIA_STOP = 86; + /** Key code constant: Play Next media key. */ + public static final int KEYCODE_MEDIA_NEXT = 87; + /** Key code constant: Play Previous media key. */ + public static final int KEYCODE_MEDIA_PREVIOUS = 88; + /** Key code constant: Rewind media key. */ + public static final int KEYCODE_MEDIA_REWIND = 89; + /** Key code constant: Fast Forward media key. */ + public static final int KEYCODE_MEDIA_FAST_FORWARD = 90; + /** Key code constant: Mute key. + * Mutes the microphone, unlike {@link #KEYCODE_VOLUME_MUTE}. */ + public static final int KEYCODE_MUTE = 91; + /** Key code constant: Page Up key. */ + public static final int KEYCODE_PAGE_UP = 92; + /** Key code constant: Page Down key. */ + public static final int KEYCODE_PAGE_DOWN = 93; + /** Key code constant: Picture Symbols modifier key. + * Used to switch symbol sets (Emoji, Kao-moji). */ + public static final int KEYCODE_PICTSYMBOLS = 94; // switch symbol-sets (Emoji,Kao-moji) + /** Key code constant: Switch Charset modifier key. + * Used to switch character sets (Kanji, Katakana). */ + public static final int KEYCODE_SWITCH_CHARSET = 95; // switch char-sets (Kanji,Katakana) + /** Key code constant: A Button key. + * On a game controller, the A button should be either the button labeled A + * or the first button on the upper row of controller buttons. */ + public static final int KEYCODE_BUTTON_A = 96; + /** Key code constant: B Button key. + * On a game controller, the B button should be either the button labeled B + * or the second button on the upper row of controller buttons. */ + public static final int KEYCODE_BUTTON_B = 97; + /** Key code constant: C Button key. + * On a game controller, the C button should be either the button labeled C + * or the third button on the upper row of controller buttons. */ + public static final int KEYCODE_BUTTON_C = 98; + /** Key code constant: X Button key. + * On a game controller, the X button should be either the button labeled X + * or the first button on the lower row of controller buttons. */ + public static final int KEYCODE_BUTTON_X = 99; + /** Key code constant: Y Button key. + * On a game controller, the Y button should be either the button labeled Y + * or the second button on the lower row of controller buttons. */ + public static final int KEYCODE_BUTTON_Y = 100; + /** Key code constant: Z Button key. + * On a game controller, the Z button should be either the button labeled Z + * or the third button on the lower row of controller buttons. */ + public static final int KEYCODE_BUTTON_Z = 101; + /** Key code constant: L1 Button key. + * On a game controller, the L1 button should be either the button labeled L1 (or L) + * or the top left trigger button. */ + public static final int KEYCODE_BUTTON_L1 = 102; + /** Key code constant: R1 Button key. + * On a game controller, the R1 button should be either the button labeled R1 (or R) + * or the top right trigger button. */ + public static final int KEYCODE_BUTTON_R1 = 103; + /** Key code constant: L2 Button key. + * On a game controller, the L2 button should be either the button labeled L2 + * or the bottom left trigger button. */ + public static final int KEYCODE_BUTTON_L2 = 104; + /** Key code constant: R2 Button key. + * On a game controller, the R2 button should be either the button labeled R2 + * or the bottom right trigger button. */ + public static final int KEYCODE_BUTTON_R2 = 105; + /** Key code constant: Left Thumb Button key. + * On a game controller, the left thumb button indicates that the left (or only) + * joystick is pressed. */ + public static final int KEYCODE_BUTTON_THUMBL = 106; + /** Key code constant: Right Thumb Button key. + * On a game controller, the right thumb button indicates that the right + * joystick is pressed. */ + public static final int KEYCODE_BUTTON_THUMBR = 107; + /** Key code constant: Start Button key. + * On a game controller, the button labeled Start. */ + public static final int KEYCODE_BUTTON_START = 108; + /** Key code constant: Select Button key. + * On a game controller, the button labeled Select. */ + public static final int KEYCODE_BUTTON_SELECT = 109; + /** Key code constant: Mode Button key. + * On a game controller, the button labeled Mode. */ + public static final int KEYCODE_BUTTON_MODE = 110; + /** Key code constant: Escape key. */ + public static final int KEYCODE_ESCAPE = 111; + /** Key code constant: Forward Delete key. + * Deletes characters ahead of the insertion point, unlike {@link #KEYCODE_DEL}. */ + public static final int KEYCODE_FORWARD_DEL = 112; + /** Key code constant: Left Control modifier key. */ + public static final int KEYCODE_CTRL_LEFT = 113; + /** Key code constant: Right Control modifier key. */ + public static final int KEYCODE_CTRL_RIGHT = 114; + /** Key code constant: Caps Lock modifier key. */ + public static final int KEYCODE_CAPS_LOCK = 115; + /** Key code constant: Scroll Lock key. */ + public static final int KEYCODE_SCROLL_LOCK = 116; + /** Key code constant: Left Meta modifier key. */ + public static final int KEYCODE_META_LEFT = 117; + /** Key code constant: Right Meta modifier key. */ + public static final int KEYCODE_META_RIGHT = 118; + /** Key code constant: Function modifier key. */ + public static final int KEYCODE_FUNCTION = 119; + /** Key code constant: System Request / Print Screen key. */ + public static final int KEYCODE_SYSRQ = 120; + /** Key code constant: Break / Pause key. */ + public static final int KEYCODE_BREAK = 121; + /** Key code constant: Home Movement key. + * Used for scrolling or moving the cursor around to the start of a line + * or to the top of a list. */ + public static final int KEYCODE_MOVE_HOME = 122; + /** Key code constant: End Movement key. + * Used for scrolling or moving the cursor around to the end of a line + * or to the bottom of a list. */ + public static final int KEYCODE_MOVE_END = 123; + /** Key code constant: Insert key. + * Toggles insert / overwrite edit mode. */ + public static final int KEYCODE_INSERT = 124; + /** Key code constant: Forward key. + * Navigates forward in the history stack. Complement of {@link #KEYCODE_BACK}. */ + public static final int KEYCODE_FORWARD = 125; + /** Key code constant: Play media key. */ + public static final int KEYCODE_MEDIA_PLAY = 126; + /** Key code constant: Pause media key. */ + public static final int KEYCODE_MEDIA_PAUSE = 127; + /** Key code constant: Close media key. + * May be used to close a CD tray, for example. */ + public static final int KEYCODE_MEDIA_CLOSE = 128; + /** Key code constant: Eject media key. + * May be used to eject a CD tray, for example. */ + public static final int KEYCODE_MEDIA_EJECT = 129; + /** Key code constant: Record media key. */ + public static final int KEYCODE_MEDIA_RECORD = 130; + /** Key code constant: F1 key. */ + public static final int KEYCODE_F1 = 131; + /** Key code constant: F2 key. */ + public static final int KEYCODE_F2 = 132; + /** Key code constant: F3 key. */ + public static final int KEYCODE_F3 = 133; + /** Key code constant: F4 key. */ + public static final int KEYCODE_F4 = 134; + /** Key code constant: F5 key. */ + public static final int KEYCODE_F5 = 135; + /** Key code constant: F6 key. */ + public static final int KEYCODE_F6 = 136; + /** Key code constant: F7 key. */ + public static final int KEYCODE_F7 = 137; + /** Key code constant: F8 key. */ + public static final int KEYCODE_F8 = 138; + /** Key code constant: F9 key. */ + public static final int KEYCODE_F9 = 139; + /** Key code constant: F10 key. */ + public static final int KEYCODE_F10 = 140; + /** Key code constant: F11 key. */ + public static final int KEYCODE_F11 = 141; + /** Key code constant: F12 key. */ + public static final int KEYCODE_F12 = 142; + /** Key code constant: Num Lock modifier key. + * This is the Num Lock key; it is different from {@link #KEYCODE_NUM}. + * This key generally modifies the behavior of other keys on the numeric keypad. */ + public static final int KEYCODE_NUM_LOCK = 143; + /** Key code constant: Numeric keypad '0' key. */ + public static final int KEYCODE_NUMPAD_0 = 144; + /** Key code constant: Numeric keypad '1' key. */ + public static final int KEYCODE_NUMPAD_1 = 145; + /** Key code constant: Numeric keypad '2' key. */ + public static final int KEYCODE_NUMPAD_2 = 146; + /** Key code constant: Numeric keypad '3' key. */ + public static final int KEYCODE_NUMPAD_3 = 147; + /** Key code constant: Numeric keypad '4' key. */ + public static final int KEYCODE_NUMPAD_4 = 148; + /** Key code constant: Numeric keypad '5' key. */ + public static final int KEYCODE_NUMPAD_5 = 149; + /** Key code constant: Numeric keypad '6' key. */ + public static final int KEYCODE_NUMPAD_6 = 150; + /** Key code constant: Numeric keypad '7' key. */ + public static final int KEYCODE_NUMPAD_7 = 151; + /** Key code constant: Numeric keypad '8' key. */ + public static final int KEYCODE_NUMPAD_8 = 152; + /** Key code constant: Numeric keypad '9' key. */ + public static final int KEYCODE_NUMPAD_9 = 153; + /** Key code constant: Numeric keypad '/' key (for division). */ + public static final int KEYCODE_NUMPAD_DIVIDE = 154; + /** Key code constant: Numeric keypad '*' key (for multiplication). */ + public static final int KEYCODE_NUMPAD_MULTIPLY = 155; + /** Key code constant: Numeric keypad '-' key (for subtraction). */ + public static final int KEYCODE_NUMPAD_SUBTRACT = 156; + /** Key code constant: Numeric keypad '+' key (for addition). */ + public static final int KEYCODE_NUMPAD_ADD = 157; + /** Key code constant: Numeric keypad '.' key (for decimals or digit grouping). */ + public static final int KEYCODE_NUMPAD_DOT = 158; + /** Key code constant: Numeric keypad ',' key (for decimals or digit grouping). */ + public static final int KEYCODE_NUMPAD_COMMA = 159; + /** Key code constant: Numeric keypad Enter key. */ + public static final int KEYCODE_NUMPAD_ENTER = 160; + /** Key code constant: Numeric keypad '=' key. */ + public static final int KEYCODE_NUMPAD_EQUALS = 161; + /** Key code constant: Numeric keypad '(' key. */ + public static final int KEYCODE_NUMPAD_LEFT_PAREN = 162; + /** Key code constant: Numeric keypad ')' key. */ + public static final int KEYCODE_NUMPAD_RIGHT_PAREN = 163; + /** Key code constant: Volume Mute key. + * Mutes the speaker, unlike {@link #KEYCODE_MUTE}. + * This key should normally be implemented as a toggle such that the first press + * mutes the speaker and the second press restores the original volume. */ + public static final int KEYCODE_VOLUME_MUTE = 164; + /** Key code constant: Info key. + * Common on TV remotes to show additional information related to what is + * currently being viewed. */ + public static final int KEYCODE_INFO = 165; + /** Key code constant: Channel up key. + * On TV remotes, increments the television channel. */ + public static final int KEYCODE_CHANNEL_UP = 166; + /** Key code constant: Channel down key. + * On TV remotes, decrements the television channel. */ + public static final int KEYCODE_CHANNEL_DOWN = 167; + /** Key code constant: Zoom in key. */ + public static final int KEYCODE_ZOOM_IN = 168; + /** Key code constant: Zoom out key. */ + public static final int KEYCODE_ZOOM_OUT = 169; + /** Key code constant: TV key. + * On TV remotes, switches to viewing live TV. */ + public static final int KEYCODE_TV = 170; + /** Key code constant: Window key. + * On TV remotes, toggles picture-in-picture mode or other windowing functions. */ + public static final int KEYCODE_WINDOW = 171; + /** Key code constant: Guide key. + * On TV remotes, shows a programming guide. */ + public static final int KEYCODE_GUIDE = 172; + /** Key code constant: DVR key. + * On some TV remotes, switches to a DVR mode for recorded shows. */ + public static final int KEYCODE_DVR = 173; + /** Key code constant: Bookmark key. + * On some TV remotes, bookmarks content or web pages. */ + public static final int KEYCODE_BOOKMARK = 174; + /** Key code constant: Toggle captions key. + * Switches the mode for closed-captioning text, for example during television shows. */ + public static final int KEYCODE_CAPTIONS = 175; + /** Key code constant: Settings key. + * Starts the system settings activity. */ + public static final int KEYCODE_SETTINGS = 176; + /** Key code constant: TV power key. + * On TV remotes, toggles the power on a television screen. */ + public static final int KEYCODE_TV_POWER = 177; + /** Key code constant: TV input key. + * On TV remotes, switches the input on a television screen. */ + public static final int KEYCODE_TV_INPUT = 178; + /** Key code constant: Set-top-box power key. + * On TV remotes, toggles the power on an external Set-top-box. */ + public static final int KEYCODE_STB_POWER = 179; + /** Key code constant: Set-top-box input key. + * On TV remotes, switches the input mode on an external Set-top-box. */ + public static final int KEYCODE_STB_INPUT = 180; + /** Key code constant: A/V Receiver power key. + * On TV remotes, toggles the power on an external A/V Receiver. */ + public static final int KEYCODE_AVR_POWER = 181; + /** Key code constant: A/V Receiver input key. + * On TV remotes, switches the input mode on an external A/V Receiver. */ + public static final int KEYCODE_AVR_INPUT = 182; + /** Key code constant: Red "programmable" key. + * On TV remotes, acts as a contextual/programmable key. */ + public static final int KEYCODE_PROG_RED = 183; + /** Key code constant: Green "programmable" key. + * On TV remotes, actsas a contextual/programmable key. */ + public static final int KEYCODE_PROG_GREEN = 184; + /** Key code constant: Yellow "programmable" key. + * On TV remotes, acts as a contextual/programmable key. */ + public static final int KEYCODE_PROG_YELLOW = 185; + /** Key code constant: Blue "programmable" key. + * On TV remotes, acts as a contextual/programmable key. */ + public static final int KEYCODE_PROG_BLUE = 186; + + private static final int LAST_KEYCODE = KEYCODE_PROG_BLUE; + + private String[] mKeyCodes = new String[256]; + private String[] mAppKeyCodes = new String[256]; + + private void initKeyCodes() { + mKeyCodes[KEYCODE_DPAD_CENTER] = "\015"; + mKeyCodes[KEYCODE_DPAD_UP] = "\033[A"; + mKeyCodes[KEYCODE_DPAD_DOWN] = "\033[B"; + mKeyCodes[KEYCODE_DPAD_RIGHT] = "\033[C"; + mKeyCodes[KEYCODE_DPAD_LEFT] = "\033[D"; + mKeyCodes[KEYCODE_F1] = "\033[OP"; + mKeyCodes[KEYCODE_F2] = "\033[OQ"; + mKeyCodes[KEYCODE_F3] = "\033[OR"; + mKeyCodes[KEYCODE_F4] = "\033[OS"; + mKeyCodes[KEYCODE_F5] = "\033[15~"; + mKeyCodes[KEYCODE_F6] = "\033[17~"; + mKeyCodes[KEYCODE_F7] = "\033[18~"; + mKeyCodes[KEYCODE_F8] = "\033[19~"; + mKeyCodes[KEYCODE_F9] = "\033[20~"; + mKeyCodes[KEYCODE_F10] = "\033[21~"; + mKeyCodes[KEYCODE_F11] = "\033[23~"; + mKeyCodes[KEYCODE_F12] = "\033[24~"; + mKeyCodes[KEYCODE_SYSRQ] = "\033[32~"; // Sys Request / Print + // Is this Scroll lock? mKeyCodes[Cancel] = "\033[33~"; + mKeyCodes[KEYCODE_BREAK] = "\033[34~"; // Pause/Break + + mKeyCodes[KEYCODE_TAB] = "\011"; + mKeyCodes[KEYCODE_ENTER] = "\015"; + mKeyCodes[KEYCODE_ESCAPE] = "\033"; + + mKeyCodes[KEYCODE_INSERT] = "\033[2~"; + mKeyCodes[KEYCODE_FORWARD_DEL] = "\033[3~"; + mKeyCodes[KEYCODE_MOVE_HOME] = "\033[1~"; + mKeyCodes[KEYCODE_MOVE_END] = "\033[4~"; + mKeyCodes[KEYCODE_PAGE_UP] = "\033[5~"; + mKeyCodes[KEYCODE_PAGE_DOWN] = "\033[6~"; + mKeyCodes[KEYCODE_DEL]= "\177"; + mKeyCodes[KEYCODE_NUM_LOCK] = "\033OP"; + mKeyCodes[KEYCODE_NUMPAD_DIVIDE] = "/"; + mKeyCodes[KEYCODE_NUMPAD_MULTIPLY] = "*"; + mKeyCodes[KEYCODE_NUMPAD_SUBTRACT] = "-"; + mKeyCodes[KEYCODE_NUMPAD_ADD] = "+"; + mKeyCodes[KEYCODE_NUMPAD_ENTER] = "\015"; + mKeyCodes[KEYCODE_NUMPAD_EQUALS] = "="; + mKeyCodes[KEYCODE_NUMPAD_DOT] = "."; + mKeyCodes[KEYCODE_NUMPAD_COMMA] = ","; + mKeyCodes[KEYCODE_NUMPAD_0] = "0"; + mKeyCodes[KEYCODE_NUMPAD_1] = "1"; + mKeyCodes[KEYCODE_NUMPAD_2] = "2"; + mKeyCodes[KEYCODE_NUMPAD_3] = "3"; + mKeyCodes[KEYCODE_NUMPAD_4] = "4"; + mKeyCodes[KEYCODE_NUMPAD_5] = "5"; + mKeyCodes[KEYCODE_NUMPAD_6] = "6"; + mKeyCodes[KEYCODE_NUMPAD_7] = "7"; + mKeyCodes[KEYCODE_NUMPAD_8] = "8"; + mKeyCodes[KEYCODE_NUMPAD_9] = "9"; + + mAppKeyCodes[KEYCODE_DPAD_UP] = "\033OA"; + mAppKeyCodes[KEYCODE_DPAD_DOWN] = "\033OB"; + mAppKeyCodes[KEYCODE_DPAD_RIGHT] = "\033OC"; + mAppKeyCodes[KEYCODE_DPAD_LEFT] = "\033OD"; + mAppKeyCodes[KEYCODE_NUMPAD_DIVIDE] = "\033Oo"; + mAppKeyCodes[KEYCODE_NUMPAD_MULTIPLY] = "\033Oj"; + mAppKeyCodes[KEYCODE_NUMPAD_SUBTRACT] = "\033Om"; + mAppKeyCodes[KEYCODE_NUMPAD_ADD] = "\033Ok"; + mAppKeyCodes[KEYCODE_NUMPAD_ENTER] = "\033OM"; + mAppKeyCodes[KEYCODE_NUMPAD_EQUALS] = "\033OX"; + mAppKeyCodes[KEYCODE_NUMPAD_DOT] = "\033On"; + mAppKeyCodes[KEYCODE_NUMPAD_COMMA] = "\033Ol"; + mAppKeyCodes[KEYCODE_NUMPAD_0] = "\033Op"; + mAppKeyCodes[KEYCODE_NUMPAD_1] = "\033Oq"; + mAppKeyCodes[KEYCODE_NUMPAD_2] = "\033Or"; + mAppKeyCodes[KEYCODE_NUMPAD_3] = "\033Os"; + mAppKeyCodes[KEYCODE_NUMPAD_4] = "\033Ot"; + mAppKeyCodes[KEYCODE_NUMPAD_5] = "\033Ou"; + mAppKeyCodes[KEYCODE_NUMPAD_6] = "\033Ov"; + mAppKeyCodes[KEYCODE_NUMPAD_7] = "\033Ow"; + mAppKeyCodes[KEYCODE_NUMPAD_8] = "\033Ox"; + mAppKeyCodes[KEYCODE_NUMPAD_9] = "\033Oy"; + } + + /** + * The state engine for a modifier key. Can be pressed, released, locked, + * and so on. + * + */ + private class ModifierKey { + + private int mState; + + private static final int UNPRESSED = 0; + + private static final int PRESSED = 1; + + private static final int RELEASED = 2; + + private static final int USED = 3; + + private static final int LOCKED = 4; + + /** + * Construct a modifier key. UNPRESSED by default. + * + */ + public ModifierKey() { + mState = UNPRESSED; + } + + public void onPress() { + switch (mState) { + case PRESSED: + // This is a repeat before use + break; + case RELEASED: + mState = LOCKED; + break; + case USED: + // This is a repeat after use + break; + case LOCKED: + mState = UNPRESSED; + break; + default: + mState = PRESSED; + break; + } + } + + public void onRelease() { + switch (mState) { + case USED: + mState = UNPRESSED; + break; + case PRESSED: + mState = RELEASED; + break; + default: + // Leave state alone + break; + } + } + + public void adjustAfterKeypress() { + switch (mState) { + case PRESSED: + mState = USED; + break; + case RELEASED: + mState = UNPRESSED; + break; + default: + // Leave state alone + break; + } + } + + public boolean isActive() { + return mState != UNPRESSED; + } + } + + private ModifierKey mAltKey = new ModifierKey(); + + private ModifierKey mCapKey = new ModifierKey(); + + private ModifierKey mControlKey = new ModifierKey(); + + private ModifierKey mFnKey = new ModifierKey(); + + private boolean mCapsLock; + + static public final int KEYCODE_OFFSET = 1000; + + /** + * Construct a term key listener. + * + */ + public TermKeyListener() { + initKeyCodes(); + } + + public void handleControlKey(boolean down) { + if (down) { + mControlKey.onPress(); + } else { + mControlKey.onRelease(); + } + } + + public void handleFnKey(boolean down) { + if (down) { + mFnKey.onPress(); + } else { + mFnKey.onRelease(); + } + } + + public int mapControlChar(int ch) { + int result = ch; + if (mControlKey.isActive()) { + // Search is the control key. + if (result >= 'a' && result <= 'z') { + result = (char) (result - 'a' + '\001'); + } else if (result >= 'A' && result <= 'Z') { + result = (char) (result - 'A' + '\001'); + } else if (result == ' ' || result == '2') { + result = 0; + } else if (result == '[' || result == '3') { + result = 27; // ^[ (Esc) + } else if (result == '\\' || result == '4') { + result = 28; + } else if (result == ']' || result == '5') { + result = 29; + } else if (result == '^' || result == '6') { + result = 30; // control-^ + } else if (result == '_' || result == '7') { + result = 31; + } else if (result == '8') { + result = 127; // DEL + } else if (result == '9') { + result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_F11; + } else if (result == '0') { + result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_F12; + } + } else if (mFnKey.isActive()) { + if (result == 'w' || result == 'W') { + result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_UP; + } else if (result == 'a' || result == 'A') { + result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_LEFT; + } else if (result == 's' || result == 'S') { + result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_DOWN; + } else if (result == 'd' || result == 'D') { + result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_RIGHT; + } else if (result == 'p' || result == 'P') { + result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_PAGE_UP; + } else if (result == 'n' || result == 'N') { + result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_PAGE_DOWN; + } else if (result == 't' || result == 'T') { + result = KEYCODE_OFFSET + KeyEvent.KEYCODE_TAB; + } else if (result == 'l' || result == 'L') { + result = '|'; + } else if (result == 'u' || result == 'U') { + result = '_'; + } else if (result == 'e' || result == 'E') { + result = 27; // ^[ (Esc) + } else if (result == '.') { + result = 28; // ^\ + } else if (result > '0' && result <= '9') { + // F1-F9 + result = (char)(result + KEYCODE_OFFSET + TermKeyListener.KEYCODE_F1 - 1); + } else if (result == '0') { + result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_F10; + } else if (result == 'i' || result == 'I') { + result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_INSERT; + } else if (result == 'x' || result == 'X') { + result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_FORWARD_DEL; + } else if (result == 'h' || result == 'H') { + result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_MOVE_HOME; + } else if (result == 'f' || result == 'F') { + result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_MOVE_END; + } + } + + if (result > -1) { + mAltKey.adjustAfterKeypress(); + mCapKey.adjustAfterKeypress(); + mControlKey.adjustAfterKeypress(); + mFnKey.adjustAfterKeypress(); + } + + return result; + } + + /** + * Handle a keyDown event. + * + * @param keyCode the keycode of the keyDown event + * + */ + public void keyDown(int keyCode, KeyEvent event, OutputStream out, boolean appMode) throws IOException { + if (handleKeyCode(keyCode, out, appMode)) { + return; + } + int result = -1; + switch (keyCode) { + case KeyEvent.KEYCODE_ALT_RIGHT: + case KeyEvent.KEYCODE_ALT_LEFT: + mAltKey.onPress(); + break; + + case KeyEvent.KEYCODE_SHIFT_LEFT: + case KeyEvent.KEYCODE_SHIFT_RIGHT: + mCapKey.onPress(); + break; + + case KEYCODE_CTRL_LEFT: + case KEYCODE_CTRL_RIGHT: + mControlKey.onPress(); + break; + + case KEYCODE_CAPS_LOCK: + if (event.getRepeatCount() == 0) { + mCapsLock = !mCapsLock; + } + break; + + default: { + result = event.getUnicodeChar( + (mCapKey.isActive() || mCapsLock ? KeyEvent.META_SHIFT_ON : 0) | + (mAltKey.isActive() ? KeyEvent.META_ALT_ON : 0)); + break; + } + } + + result = mapControlChar(result); + + if (result >= KEYCODE_OFFSET) { + handleKeyCode(result - KEYCODE_OFFSET, out, appMode); + } else if (result >= 0) { + out.write(result); + } + } + + public boolean handleKeyCode(int keyCode, OutputStream out, boolean appMode) throws IOException { + if (keyCode >= 0 && keyCode < mKeyCodes.length) { + String code = null; + if (appMode) { + code = mAppKeyCodes[keyCode]; + } + if (code == null) { + code = mKeyCodes[keyCode]; + } + if (code != null) { + int length = code.length(); + for (int i = 0; i < length; i++) { + out.write(code.charAt(i)); + } + return true; + } + } + return false; + } + + /** + * Handle a keyUp event. + * + * @param keyCode the keyCode of the keyUp event + */ + public void keyUp(int keyCode) { + switch (keyCode) { + case KeyEvent.KEYCODE_ALT_LEFT: + case KeyEvent.KEYCODE_ALT_RIGHT: + mAltKey.onRelease(); + break; + case KeyEvent.KEYCODE_SHIFT_LEFT: + case KeyEvent.KEYCODE_SHIFT_RIGHT: + mCapKey.onRelease(); + break; + + case KEYCODE_CTRL_LEFT: + case KEYCODE_CTRL_RIGHT: + mControlKey.onRelease(); + break; + + default: + // Ignore other keyUps + break; + } + } +} diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index fabe014a2..584eaf782 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -17,10 +17,8 @@ package jackpal.androidterm; import java.io.FileDescriptor; -import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; -import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.util.ArrayList; @@ -31,27 +29,18 @@ import android.content.SharedPreferences; import android.content.res.Configuration; import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Canvas; -import android.graphics.ColorMatrixColorFilter; -import android.graphics.Paint; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffXfermode; -import android.graphics.Rect; -import android.graphics.Typeface; import android.net.Uri; +import android.net.wifi.WifiManager; import android.os.Bundle; import android.os.Handler; import android.os.Message; +import android.os.PowerManager; import android.preference.PreferenceManager; import android.text.ClipboardManager; -import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; -import android.view.GestureDetector; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; @@ -59,24 +48,7 @@ import android.view.View; import android.view.Window; import android.view.WindowManager; -import android.view.inputmethod.BaseInputConnection; -import android.view.inputmethod.CompletionInfo; -import android.view.inputmethod.CorrectionInfo; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.ExtractedText; -import android.view.inputmethod.ExtractedTextRequest; -import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; -import android.widget.TextView; - -import android.content.ComponentName; -import android.content.Intent; -import android.content.ServiceConnection; -import android.os.Binder; -import android.os.IBinder; - -import android.net.wifi.WifiManager; -import android.os.PowerManager; /** * A terminal emulator activity. @@ -712,3882 +684,3 @@ private void doToggleWifiLock() { } } } - - -/** - * An abstract screen interface. A terminal screen stores lines of text. (The - * reason to abstract it is to allow different implementations, and to hide - * implementation details from clients.) - */ -interface Screen { - - /** - * Set line wrap flag for a given row. Affects how lines are logically - * wrapped when changing screen size or converting to a transcript. - */ - void setLineWrap(int row); - - /** - * Store byte b into the screen at location (x, y) - * - * @param x X coordinate (also known as column) - * @param y Y coordinate (also known as row) - * @param b ASCII character to store - * @param foreColor the foreground color - * @param backColor the background color - */ - void set(int x, int y, byte b, int foreColor, int backColor); - - /** - * Scroll the screen down one line. To scroll the whole screen of a 24 line - * screen, the arguments would be (0, 24). - * - * @param topMargin First line that is scrolled. - * @param bottomMargin One line after the last line that is scrolled. - */ - void scroll(int topMargin, int bottomMargin, int foreColor, int backColor); - - /** - * Block copy characters from one position in the screen to another. The two - * positions can overlap. All characters of the source and destination must - * be within the bounds of the screen, or else an InvalidParemeterException - * will be thrown. - * - * @param sx source X coordinate - * @param sy source Y coordinate - * @param w width - * @param h height - * @param dx destination X coordinate - * @param dy destination Y coordinate - */ - void blockCopy(int sx, int sy, int w, int h, int dx, int dy); - - /** - * Block set characters. All characters must be within the bounds of the - * screen, or else and InvalidParemeterException will be thrown. Typically - * this is called with a "val" argument of 32 to clear a block of - * characters. - * - * @param sx source X - * @param sy source Y - * @param w width - * @param h height - * @param val value to set. - * @param foreColor the foreground color - * @param backColor the background color - */ - void blockSet(int sx, int sy, int w, int h, int val, int foreColor, int - backColor); - - /** - * Get the contents of the transcript buffer as a text string. - * - * @return the contents of the transcript buffer. - */ - String getTranscriptText(); - - /** - * Get the selected text inside transcript buffer as a text string. - * @param x1 Selection start - * @param y1 Selection start - * @param x2 Selection end - * @param y2 Selection end - * @return the contents of the transcript buffer. - */ - String getSelectedText(int x1, int y1, int x2, int y2); - - /** - * Resize the screen - * @param columns - * @param rows - */ - void resize(int columns, int rows, int foreColor, int backColor); -} - - -/** - * A TranscriptScreen is a screen that remembers data that's been scrolled. The - * old data is stored in a ring buffer to minimize the amount of copying that - * needs to be done. The transcript does its own drawing, to avoid having to - * expose its internal data structures. - */ -class TranscriptScreen implements Screen { - private static final String TAG = "TranscriptScreen"; - - /** - * The width of the transcript, in characters. Fixed at initialization. - */ - private int mColumns; - - /** - * The total number of rows in the transcript and the screen. Fixed at - * initialization. - */ - private int mTotalRows; - - /** - * The number of rows in the active portion of the transcript. Doesn't - * include the screen. - */ - private int mActiveTranscriptRows; - - /** - * Which row is currently the topmost line of the transcript. Used to - * implement a circular buffer. - */ - private int mHead; - - /** - * The number of active rows, includes both the transcript and the screen. - */ - private int mActiveRows; - - /** - * The number of rows in the screen. - */ - private int mScreenRows; - - /** - * The data for both the screen and the transcript. The first mScreenRows * - * mLineWidth characters are the screen, the rest are the transcript. - * The low byte encodes the ASCII character, the high byte encodes the - * foreground and background colors, plus underline and bold. - */ - private char[] mData; - - /** - * The data's stored as color-encoded chars, but the drawing routines require chars, so we - * need a temporary buffer to hold a row's worth of characters. - */ - private char[] mRowBuffer; - - /** - * Flags that keep track of whether the current line logically wraps to the - * next line. This is used when resizing the screen and when copying to the - * clipboard or an email attachment - */ - - private boolean[] mLineWrap; - - /** - * Create a transcript screen. - * - * @param columns the width of the screen in characters. - * @param totalRows the height of the entire text area, in rows of text. - * @param screenRows the height of just the screen, not including the - * transcript that holds lines that have scrolled off the top of the - * screen. - */ - public TranscriptScreen(int columns, int totalRows, int screenRows, - int foreColor, int backColor) { - init(columns, totalRows, screenRows, foreColor, backColor); - } - - private void init(int columns, int totalRows, int screenRows, int foreColor, int backColor) { - mColumns = columns; - mTotalRows = totalRows; - mActiveTranscriptRows = 0; - mHead = 0; - mActiveRows = screenRows; - mScreenRows = screenRows; - int totalSize = columns * totalRows; - mData = new char[totalSize]; - blockSet(0, 0, mColumns, mScreenRows, ' ', foreColor, backColor); - mRowBuffer = new char[columns]; - mLineWrap = new boolean[totalRows]; - consistencyCheck(); - } - - /** - * Convert a row value from the public external coordinate system to our - * internal private coordinate system. External coordinate system: - * -mActiveTranscriptRows to mScreenRows-1, with the screen being - * 0..mScreenRows-1 Internal coordinate system: 0..mScreenRows-1 rows of - * mData are the visible rows. mScreenRows..mActiveRows - 1 are the - * transcript, stored as a circular buffer. - * - * @param row a row in the external coordinate system. - * @return The row corresponding to the input argument in the private - * coordinate system. - */ - private int externalToInternalRow(int row) { - if (row < -mActiveTranscriptRows || row >= mScreenRows) { - String errorMessage = "externalToInternalRow "+ row + - " " + mActiveTranscriptRows + " " + mScreenRows; - Log.e(TAG, errorMessage); - throw new IllegalArgumentException(errorMessage); - } - if (row >= 0) { - return row; // This is a visible row. - } - return mScreenRows - + ((mHead + mActiveTranscriptRows + row) % mActiveTranscriptRows); - } - - private int getOffset(int externalLine) { - return externalToInternalRow(externalLine) * mColumns; - } - - private int getOffset(int x, int y) { - return getOffset(y) + x; - } - - public void setLineWrap(int row) { - mLineWrap[externalToInternalRow(row)] = true; - } - - /** - * Store byte b into the screen at location (x, y) - * - * @param x X coordinate (also known as column) - * @param y Y coordinate (also known as row) - * @param b ASCII character to store - * @param foreColor the foreground color - * @param backColor the background color - */ - public void set(int x, int y, byte b, int foreColor, int backColor) { - mData[getOffset(x, y)] = encode(b, foreColor, backColor); - } - - private char encode(int b, int foreColor, int backColor) { - return (char) ((foreColor << 12) | (backColor << 8) | b); - } - - /** - * Scroll the screen down one line. To scroll the whole screen of a 24 line - * screen, the arguments would be (0, 24). - * - * @param topMargin First line that is scrolled. - * @param bottomMargin One line after the last line that is scrolled. - */ - public void scroll(int topMargin, int bottomMargin, int foreColor, - int backColor) { - // Separate out reasons so that stack crawls help us - // figure out which condition was violated. - if (topMargin > bottomMargin - 1) { - throw new IllegalArgumentException(); - } - - if (topMargin > mScreenRows - 1) { - throw new IllegalArgumentException(); - } - - if (bottomMargin > mScreenRows) { - throw new IllegalArgumentException(); - } - - // Adjust the transcript so that the last line of the transcript - // is ready to receive the newly scrolled data - consistencyCheck(); - int expansionRows = Math.min(1, mTotalRows - mActiveRows); - int rollRows = 1 - expansionRows; - mActiveRows += expansionRows; - mActiveTranscriptRows += expansionRows; - if (mActiveTranscriptRows > 0) { - mHead = (mHead + rollRows) % mActiveTranscriptRows; - } - consistencyCheck(); - - // Block move the scroll line to the transcript - int topOffset = getOffset(topMargin); - int destOffset = getOffset(-1); - System.arraycopy(mData, topOffset, mData, destOffset, mColumns); - - int topLine = externalToInternalRow(topMargin); - int destLine = externalToInternalRow(-1); - System.arraycopy(mLineWrap, topLine, mLineWrap, destLine, 1); - - // Block move the scrolled data up - int numScrollChars = (bottomMargin - topMargin - 1) * mColumns; - System.arraycopy(mData, topOffset + mColumns, mData, topOffset, - numScrollChars); - int numScrollLines = (bottomMargin - topMargin - 1); - System.arraycopy(mLineWrap, topLine + 1, mLineWrap, topLine, - numScrollLines); - - // Erase the bottom line of the scroll region - blockSet(0, bottomMargin - 1, mColumns, 1, ' ', foreColor, backColor); - mLineWrap[externalToInternalRow(bottomMargin-1)] = false; - } - - private void consistencyCheck() { - checkPositive(mColumns); - checkPositive(mTotalRows); - checkRange(0, mActiveTranscriptRows, mTotalRows); - if (mActiveTranscriptRows == 0) { - checkEqual(mHead, 0); - } else { - checkRange(0, mHead, mActiveTranscriptRows-1); - } - checkEqual(mScreenRows + mActiveTranscriptRows, mActiveRows); - checkRange(0, mScreenRows, mTotalRows); - - checkEqual(mTotalRows, mLineWrap.length); - checkEqual(mTotalRows*mColumns, mData.length); - checkEqual(mColumns, mRowBuffer.length); - } - - private void checkPositive(int n) { - if (n < 0) { - throw new IllegalArgumentException("checkPositive " + n); - } - } - - private void checkRange(int a, int b, int c) { - if (a > b || b > c) { - throw new IllegalArgumentException("checkRange " + a + " <= " + b + " <= " + c); - } - } - - private void checkEqual(int a, int b) { - if (a != b) { - throw new IllegalArgumentException("checkEqual " + a + " == " + b); - } - } - - /** - * Block copy characters from one position in the screen to another. The two - * positions can overlap. All characters of the source and destination must - * be within the bounds of the screen, or else an InvalidParemeterException - * will be thrown. - * - * @param sx source X coordinate - * @param sy source Y coordinate - * @param w width - * @param h height - * @param dx destination X coordinate - * @param dy destination Y coordinate - */ - public void blockCopy(int sx, int sy, int w, int h, int dx, int dy) { - if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows - || dx < 0 || dx + w > mColumns || dy < 0 - || dy + h > mScreenRows) { - throw new IllegalArgumentException(); - } - if (sy > dy) { - // Move in increasing order - for (int y = 0; y < h; y++) { - int srcOffset = getOffset(sx, sy + y); - int dstOffset = getOffset(dx, dy + y); - System.arraycopy(mData, srcOffset, mData, dstOffset, w); - } - } else { - // Move in decreasing order - for (int y = 0; y < h; y++) { - int y2 = h - (y + 1); - int srcOffset = getOffset(sx, sy + y2); - int dstOffset = getOffset(dx, dy + y2); - System.arraycopy(mData, srcOffset, mData, dstOffset, w); - } - } - } - - /** - * Block set characters. All characters must be within the bounds of the - * screen, or else and InvalidParemeterException will be thrown. Typically - * this is called with a "val" argument of 32 to clear a block of - * characters. - * - * @param sx source X - * @param sy source Y - * @param w width - * @param h height - * @param val value to set. - */ - public void blockSet(int sx, int sy, int w, int h, int val, - int foreColor, int backColor) { - if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows) { - throw new IllegalArgumentException(); - } - char[] data = mData; - char encodedVal = encode(val, foreColor, backColor); - for (int y = 0; y < h; y++) { - int offset = getOffset(sx, sy + y); - for (int x = 0; x < w; x++) { - data[offset + x] = encodedVal; - } - } - } - - /** - * Draw a row of text. Out-of-bounds rows are blank, not errors. - * - * @param row The row of text to draw. - * @param canvas The canvas to draw to. - * @param x The x coordinate origin of the drawing - * @param y The y coordinate origin of the drawing - * @param renderer The renderer to use to draw the text - * @param cx the cursor X coordinate, -1 means don't draw it - * @param selx1 the text selection start X coordinate - * @param selx2 the text selection end X coordinate, if equals to selx1 don't draw selection - * @param imeText current IME text, to be rendered at cursor - */ - public final void drawText(int row, Canvas canvas, float x, float y, - TextRenderer renderer, int cx, int selx1, int selx2, String imeText) { - - // Out-of-bounds rows are blank. - if (row < -mActiveTranscriptRows || row >= mScreenRows) { - return; - } - - // Copy the data from the byte array to a char array so they can - // be drawn. - - int offset = getOffset(row); - char[] rowBuffer = mRowBuffer; - char[] data = mData; - int columns = mColumns; - int lastColors = 0; - int lastRunStart = -1; - final int CURSOR_MASK = 0x10000; - for (int i = 0; i < columns; i++) { - char c = data[offset + i]; - int colors = (char) (c & 0xff00); - if (cx == i || (i >= selx1 && i <= selx2)) { - // Set cursor background color: - colors |= CURSOR_MASK; - } - rowBuffer[i] = (char) (c & 0x00ff); - if (colors != lastColors) { - if (lastRunStart >= 0) { - renderer.drawTextRun(canvas, x, y, lastRunStart, rowBuffer, - lastRunStart, i - lastRunStart, - (lastColors & CURSOR_MASK) != 0, - 0xf & (lastColors >> 12), 0xf & (lastColors >> 8)); - } - lastColors = colors; - lastRunStart = i; - } - } - if (lastRunStart >= 0) { - renderer.drawTextRun(canvas, x, y, lastRunStart, rowBuffer, - lastRunStart, columns - lastRunStart, - (lastColors & CURSOR_MASK) != 0, - 0xf & (lastColors >> 12), 0xf & (lastColors >> 8)); - } - - if (cx >= 0 && imeText.length() > 0) { - int imeLength = Math.min(columns, imeText.length()); - int imeOffset = imeText.length() - imeLength; - int imePosition = Math.min(cx, columns - imeLength); - renderer.drawTextRun(canvas, x, y, imePosition, imeText.toCharArray(), - imeOffset, imeLength, true, 0x0f, 0x00); - } - } - - /** - * Get the count of active rows. - * - * @return the count of active rows. - */ - public int getActiveRows() { - return mActiveRows; - } - - /** - * Get the count of active transcript rows. - * - * @return the count of active transcript rows. - */ - public int getActiveTranscriptRows() { - return mActiveTranscriptRows; - } - - public String getTranscriptText() { - return internalGetTranscriptText(true, 0, -mActiveTranscriptRows, mColumns, mScreenRows); - } - - public String getSelectedText(int selX1, int selY1, int selX2, int selY2) { - return internalGetTranscriptText(true, selX1, selY1, selX2, selY2); - } - - private String internalGetTranscriptText(boolean stripColors, int selX1, int selY1, int selX2, int selY2) { - StringBuilder builder = new StringBuilder(); - char[] rowBuffer = mRowBuffer; - char[] data = mData; - int columns = mColumns; - for (int row = -mActiveTranscriptRows; row < mScreenRows; row++) { - int offset = getOffset(row); - int lastPrintingChar = -1; - for (int column = 0; column < columns; column++) { - char c = data[offset + column]; - if (stripColors) { - c = (char) (c & 0xff); - } - if ((c & 0xff) != ' ') { - lastPrintingChar = column; - } - rowBuffer[column] = c; - } - if ( row >= selY1 && row <= selY2 ) { - int x1 = 0; - int x2 = 0; - if ( row == selY1 ) { - x1 = selX1; - } - if ( row == selY2 ) { - x2 = selX2; - } else { - x2 = columns; - } - if (mLineWrap[externalToInternalRow(row)]) { - builder.append(rowBuffer, x1, x2 - x1); - } else { - builder.append(rowBuffer, x1, Math.max(0, Math.min(x2 - x1 + 1, lastPrintingChar + 1 - x1))); - builder.append('\n'); - } - } - } - return builder.toString(); - } - - public void resize(int columns, int rows, int foreColor, int backColor) { - init(columns, mTotalRows, rows, foreColor, backColor); - } -} - -/** - * Renders text into a screen. Contains all the terminal-specific knowlege and - * state. Emulates a subset of the X Window System xterm terminal, which in turn - * is an emulator for a subset of the Digital Equipment Corporation vt100 - * terminal. Missing functionality: text attributes (bold, underline, reverse - * video, color) alternate screen cursor key and keypad escape sequences. - */ -class TerminalEmulator { - - /** - * The cursor row. Numbered 0..mRows-1. - */ - private int mCursorRow; - - /** - * The cursor column. Numbered 0..mColumns-1. - */ - private int mCursorCol; - - /** - * The number of character rows in the terminal screen. - */ - private int mRows; - - /** - * The number of character columns in the terminal screen. - */ - private int mColumns; - - /** - * Used to send data to the remote process. Needed to implement the various - * "report" escape sequences. - */ - private FileOutputStream mTermOut; - - /** - * Stores the characters that appear on the screen of the emulated terminal. - */ - private Screen mScreen; - - /** - * Keeps track of the current argument of the current escape sequence. - * Ranges from 0 to MAX_ESCAPE_PARAMETERS-1. (Typically just 0 or 1.) - */ - private int mArgIndex; - - /** - * The number of parameter arguments. This name comes from the ANSI standard - * for terminal escape codes. - */ - private static final int MAX_ESCAPE_PARAMETERS = 16; - - /** - * Holds the arguments of the current escape sequence. - */ - private int[] mArgs = new int[MAX_ESCAPE_PARAMETERS]; - - // Escape processing states: - - /** - * Escape processing state: Not currently in an escape sequence. - */ - private static final int ESC_NONE = 0; - - /** - * Escape processing state: Have seen an ESC character - */ - private static final int ESC = 1; - - /** - * Escape processing state: Have seen ESC POUND - */ - private static final int ESC_POUND = 2; - - /** - * Escape processing state: Have seen ESC and a character-set-select char - */ - private static final int ESC_SELECT_LEFT_PAREN = 3; - - /** - * Escape processing state: Have seen ESC and a character-set-select char - */ - private static final int ESC_SELECT_RIGHT_PAREN = 4; - - /** - * Escape processing state: ESC [ - */ - private static final int ESC_LEFT_SQUARE_BRACKET = 5; - - /** - * Escape processing state: ESC [ ? - */ - private static final int ESC_LEFT_SQUARE_BRACKET_QUESTION_MARK = 6; - - /** - * True if the current escape sequence should continue, false if the current - * escape sequence should be terminated. Used when parsing a single - * character. - */ - private boolean mContinueSequence; - - /** - * The current state of the escape sequence state machine. - */ - private int mEscapeState; - - /** - * Saved state of the cursor row, Used to implement the save/restore cursor - * position escape sequences. - */ - private int mSavedCursorRow; - - /** - * Saved state of the cursor column, Used to implement the save/restore - * cursor position escape sequences. - */ - private int mSavedCursorCol; - - // DecSet booleans - - /** - * This mask indicates 132-column mode is set. (As opposed to 80-column - * mode.) - */ - private static final int K_132_COLUMN_MODE_MASK = 1 << 3; - - /** - * This mask indicates that origin mode is set. (Cursor addressing is - * relative to the absolute screen size, rather than the currently set top - * and bottom margins.) - */ - private static final int K_ORIGIN_MODE_MASK = 1 << 6; - - /** - * This mask indicates that wraparound mode is set. (As opposed to - * stop-at-right-column mode.) - */ - private static final int K_WRAPAROUND_MODE_MASK = 1 << 7; - - /** - * Holds multiple DECSET flags. The data is stored this way, rather than in - * separate booleans, to make it easier to implement the save-and-restore - * semantics. The various k*ModeMask masks can be used to extract and modify - * the individual flags current states. - */ - private int mDecFlags; - - /** - * Saves away a snapshot of the DECSET flags. Used to implement save and - * restore escape sequences. - */ - private int mSavedDecFlags; - - // Modes set with Set Mode / Reset Mode - - /** - * True if insert mode (as opposed to replace mode) is active. In insert - * mode new characters are inserted, pushing existing text to the right. - */ - private boolean mInsertMode; - - /** - * Automatic newline mode. Configures whether pressing return on the - * keyboard automatically generates a return as well. Not currently - * implemented. - */ - private boolean mAutomaticNewlineMode; - - /** - * An array of tab stops. mTabStop[i] is true if there is a tab stop set for - * column i. - */ - private boolean[] mTabStop; - - // The margins allow portions of the screen to be locked. - - /** - * The top margin of the screen, for scrolling purposes. Ranges from 0 to - * mRows-2. - */ - private int mTopMargin; - - /** - * The bottom margin of the screen, for scrolling purposes. Ranges from - * mTopMargin + 2 to mRows. (Defines the first row after the scrolling - * region. - */ - private int mBottomMargin; - - /** - * True if the next character to be emitted will be automatically wrapped to - * the next line. Used to disambiguate the case where the cursor is - * positioned on column mColumns-1. - */ - private boolean mAboutToAutoWrap; - - /** - * Used for debugging, counts how many chars have been processed. - */ - private int mProcessedCharCount; - - /** - * Foreground color, 0..7, mask with 8 for bold - */ - private int mForeColor; - - /** - * Background color, 0..7, mask with 8 for underline - */ - private int mBackColor; - - private boolean mInverseColors; - - private boolean mbKeypadApplicationMode; - - private boolean mAlternateCharSet; - - /** - * Used for moving selection up along with the scrolling text - */ - private int mScrollCounter = 0; - - /** - * Construct a terminal emulator that uses the supplied screen - * - * @param screen the screen to render characters into. - * @param columns the number of columns to emulate - * @param rows the number of rows to emulate - * @param termOut the output file descriptor that talks to the pseudo-tty. - */ - public TerminalEmulator(Screen screen, int columns, int rows, - FileOutputStream termOut) { - mScreen = screen; - mRows = rows; - mColumns = columns; - mTabStop = new boolean[mColumns]; - mTermOut = termOut; - reset(); - } - - public void updateSize(int columns, int rows) { - if (mRows == rows && mColumns == columns) { - return; - } - if (columns <= 0) { - throw new IllegalArgumentException("rows:" + columns); - } - - if (rows <= 0) { - throw new IllegalArgumentException("rows:" + rows); - } - - String transcriptText = mScreen.getTranscriptText(); - - mScreen.resize(columns, rows, mForeColor, mBackColor); - - if (mRows != rows) { - mRows = rows; - mTopMargin = 0; - mBottomMargin = mRows; - } - if (mColumns != columns) { - int oldColumns = mColumns; - mColumns = columns; - boolean[] oldTabStop = mTabStop; - mTabStop = new boolean[mColumns]; - int toTransfer = Math.min(oldColumns, columns); - System.arraycopy(oldTabStop, 0, mTabStop, 0, toTransfer); - while (mCursorCol >= columns) { - mCursorCol -= columns; - mCursorRow = Math.min(mBottomMargin-1, mCursorRow + 1); - } - } - mCursorRow = 0; - mCursorCol = 0; - mAboutToAutoWrap = false; - - int end = transcriptText.length()-1; - while ((end >= 0) && transcriptText.charAt(end) == '\n') { - end--; - } - for(int i = 0; i <= end; i++) { - byte c = (byte) transcriptText.charAt(i); - if (c == '\n') { - setCursorCol(0); - doLinefeed(); - } else { - emit(c); - } - } - } - - /** - * Get the cursor's current row. - * - * @return the cursor's current row. - */ - public final int getCursorRow() { - return mCursorRow; - } - - /** - * Get the cursor's current column. - * - * @return the cursor's current column. - */ - public final int getCursorCol() { - return mCursorCol; - } - - public final boolean getKeypadApplicationMode() { - return mbKeypadApplicationMode; - } - - private void setDefaultTabStops() { - for (int i = 0; i < mColumns; i++) { - mTabStop[i] = (i & 7) == 0 && i != 0; - } - } - - /** - * Accept bytes (typically from the pseudo-teletype) and process them. - * - * @param buffer a byte array containing the bytes to be processed - * @param base the first index of the array to process - * @param length the number of bytes in the array to process - */ - public void append(byte[] buffer, int base, int length) { - for (int i = 0; i < length; i++) { - byte b = buffer[base + i]; - try { - if (Term.LOG_CHARACTERS_FLAG) { - char printableB = (char) b; - if (b < 32 || b > 126) { - printableB = ' '; - } - Log.w(Term.LOG_TAG, "'" + Character.toString(printableB) - + "' (" + Integer.toString(b) + ")"); - } - process(b); - mProcessedCharCount++; - } catch (Exception e) { - Log.e(Term.LOG_TAG, "Exception while processing character " - + Integer.toString(mProcessedCharCount) + " code " - + Integer.toString(b), e); - } - } - } - - private void process(byte b) { - switch (b) { - case 0: // NUL - // Do nothing - break; - - case 7: // BEL - // Do nothing - break; - - case 8: // BS - setCursorCol(Math.max(0, mCursorCol - 1)); - break; - - case 9: // HT - // Move to next tab stop, but not past edge of screen - setCursorCol(nextTabStop(mCursorCol)); - break; - - case 13: - setCursorCol(0); - break; - - case 10: // CR - case 11: // VT - case 12: // LF - doLinefeed(); - break; - - case 14: // SO: - setAltCharSet(true); - break; - - case 15: // SI: - setAltCharSet(false); - break; - - - case 24: // CAN - case 26: // SUB - if (mEscapeState != ESC_NONE) { - mEscapeState = ESC_NONE; - emit((byte) 127); - } - break; - - case 27: // ESC - // Always starts an escape sequence - startEscapeSequence(ESC); - break; - - case (byte) 0x9b: // CSI - startEscapeSequence(ESC_LEFT_SQUARE_BRACKET); - break; - - default: - mContinueSequence = false; - switch (mEscapeState) { - case ESC_NONE: - if (b >= 32) { - emit(b); - } - break; - - case ESC: - doEsc(b); - break; - - case ESC_POUND: - doEscPound(b); - break; - - case ESC_SELECT_LEFT_PAREN: - doEscSelectLeftParen(b); - break; - - case ESC_SELECT_RIGHT_PAREN: - doEscSelectRightParen(b); - break; - - case ESC_LEFT_SQUARE_BRACKET: - doEscLeftSquareBracket(b); - break; - - case ESC_LEFT_SQUARE_BRACKET_QUESTION_MARK: - doEscLSBQuest(b); - break; - - default: - unknownSequence(b); - break; - } - if (!mContinueSequence) { - mEscapeState = ESC_NONE; - } - break; - } - } - - private void setAltCharSet(boolean alternateCharSet) { - mAlternateCharSet = alternateCharSet; - } - - private int nextTabStop(int cursorCol) { - for (int i = cursorCol; i < mColumns; i++) { - if (mTabStop[i]) { - return i; - } - } - return mColumns - 1; - } - - private void doEscLSBQuest(byte b) { - int mask = getDecFlagsMask(getArg0(0)); - switch (b) { - case 'h': // Esc [ ? Pn h - DECSET - mDecFlags |= mask; - break; - - case 'l': // Esc [ ? Pn l - DECRST - mDecFlags &= ~mask; - break; - - case 'r': // Esc [ ? Pn r - restore - mDecFlags = (mDecFlags & ~mask) | (mSavedDecFlags & mask); - break; - - case 's': // Esc [ ? Pn s - save - mSavedDecFlags = (mSavedDecFlags & ~mask) | (mDecFlags & mask); - break; - - default: - parseArg(b); - break; - } - - // 132 column mode - if ((mask & K_132_COLUMN_MODE_MASK) != 0) { - // We don't actually set 132 cols, but we do want the - // side effect of clearing the screen and homing the cursor. - blockClear(0, 0, mColumns, mRows); - setCursorRowCol(0, 0); - } - - // origin mode - if ((mask & K_ORIGIN_MODE_MASK) != 0) { - // Home the cursor. - setCursorPosition(0, 0); - } - } - - private int getDecFlagsMask(int argument) { - if (argument >= 1 && argument <= 9) { - return (1 << argument); - } - - return 0; - } - - private void startEscapeSequence(int escapeState) { - mEscapeState = escapeState; - mArgIndex = 0; - for (int j = 0; j < MAX_ESCAPE_PARAMETERS; j++) { - mArgs[j] = -1; - } - } - - private void doLinefeed() { - int newCursorRow = mCursorRow + 1; - if (newCursorRow >= mBottomMargin) { - scroll(); - newCursorRow = mBottomMargin - 1; - } - setCursorRow(newCursorRow); - } - - private void continueSequence() { - mContinueSequence = true; - } - - private void continueSequence(int state) { - mEscapeState = state; - mContinueSequence = true; - } - - private void doEscSelectLeftParen(byte b) { - doSelectCharSet(true, b); - } - - private void doEscSelectRightParen(byte b) { - doSelectCharSet(false, b); - } - - private void doSelectCharSet(boolean isG0CharSet, byte b) { - switch (b) { - case 'A': // United Kingdom character set - break; - case 'B': // ASCII set - break; - case '0': // Special Graphics - break; - case '1': // Alternate character set - break; - case '2': - break; - default: - unknownSequence(b); - } - } - - private void doEscPound(byte b) { - switch (b) { - case '8': // Esc # 8 - DECALN alignment test - mScreen.blockSet(0, 0, mColumns, mRows, 'E', - getForeColor(), getBackColor()); - break; - - default: - unknownSequence(b); - break; - } - } - - private void doEsc(byte b) { - switch (b) { - case '#': - continueSequence(ESC_POUND); - break; - - case '(': - continueSequence(ESC_SELECT_LEFT_PAREN); - break; - - case ')': - continueSequence(ESC_SELECT_RIGHT_PAREN); - break; - - case '7': // DECSC save cursor - mSavedCursorRow = mCursorRow; - mSavedCursorCol = mCursorCol; - break; - - case '8': // DECRC restore cursor - setCursorRowCol(mSavedCursorRow, mSavedCursorCol); - break; - - case 'D': // INDEX - doLinefeed(); - break; - - case 'E': // NEL - setCursorCol(0); - doLinefeed(); - break; - - case 'F': // Cursor to lower-left corner of screen - setCursorRowCol(0, mBottomMargin - 1); - break; - - case 'H': // Tab set - mTabStop[mCursorCol] = true; - break; - - case 'M': // Reverse index - if (mCursorRow <= mTopMargin) { - mScreen.blockCopy(0, mTopMargin, mColumns, mBottomMargin - - (mTopMargin + 1), 0, mTopMargin + 1); - blockClear(0, mTopMargin, mColumns); - } else { - mCursorRow--; - } - - break; - - case 'N': // SS2 - unimplementedSequence(b); - break; - - case '0': // SS3 - unimplementedSequence(b); - break; - - case 'P': // Device control string - unimplementedSequence(b); - break; - - case 'Z': // return terminal ID - sendDeviceAttributes(); - break; - - case '[': - continueSequence(ESC_LEFT_SQUARE_BRACKET); - break; - - case '=': // DECKPAM - mbKeypadApplicationMode = true; - break; - - case '>' : // DECKPNM - mbKeypadApplicationMode = false; - break; - - default: - unknownSequence(b); - break; - } - } - - private void doEscLeftSquareBracket(byte b) { - switch (b) { - case '@': // ESC [ Pn @ - ICH Insert Characters - { - int charsAfterCursor = mColumns - mCursorCol; - int charsToInsert = Math.min(getArg0(1), charsAfterCursor); - int charsToMove = charsAfterCursor - charsToInsert; - mScreen.blockCopy(mCursorCol, mCursorRow, charsToMove, 1, - mCursorCol + charsToInsert, mCursorRow); - blockClear(mCursorCol, mCursorRow, charsToInsert); - } - break; - - case 'A': // ESC [ Pn A - Cursor Up - setCursorRow(Math.max(mTopMargin, mCursorRow - getArg0(1))); - break; - - case 'B': // ESC [ Pn B - Cursor Down - setCursorRow(Math.min(mBottomMargin - 1, mCursorRow + getArg0(1))); - break; - - case 'C': // ESC [ Pn C - Cursor Right - setCursorCol(Math.min(mColumns - 1, mCursorCol + getArg0(1))); - break; - - case 'D': // ESC [ Pn D - Cursor Left - setCursorCol(Math.max(0, mCursorCol - getArg0(1))); - break; - - case 'G': // ESC [ Pn G - Cursor Horizontal Absolute - setCursorCol(Math.min(Math.max(1, getArg0(1)), mColumns) - 1); - break; - - case 'H': // ESC [ Pn ; H - Cursor Position - setHorizontalVerticalPosition(); - break; - - case 'J': // ESC [ Pn J - Erase in Display - switch (getArg0(0)) { - case 0: // Clear below - blockClear(mCursorCol, mCursorRow, mColumns - mCursorCol); - blockClear(0, mCursorRow + 1, mColumns, - mBottomMargin - (mCursorRow + 1)); - break; - - case 1: // Erase from the start of the screen to the cursor. - blockClear(0, mTopMargin, mColumns, mCursorRow - mTopMargin); - blockClear(0, mCursorRow, mCursorCol + 1); - break; - - case 2: // Clear all - blockClear(0, mTopMargin, mColumns, mBottomMargin - mTopMargin); - break; - - default: - unknownSequence(b); - break; - } - break; - - case 'K': // ESC [ Pn K - Erase in Line - switch (getArg0(0)) { - case 0: // Clear to right - blockClear(mCursorCol, mCursorRow, mColumns - mCursorCol); - break; - - case 1: // Erase start of line to cursor (including cursor) - blockClear(0, mCursorRow, mCursorCol + 1); - break; - - case 2: // Clear whole line - blockClear(0, mCursorRow, mColumns); - break; - - default: - unknownSequence(b); - break; - } - break; - - case 'L': // Insert Lines - { - int linesAfterCursor = mBottomMargin - mCursorRow; - int linesToInsert = Math.min(getArg0(1), linesAfterCursor); - int linesToMove = linesAfterCursor - linesToInsert; - mScreen.blockCopy(0, mCursorRow, mColumns, linesToMove, 0, - mCursorRow + linesToInsert); - blockClear(0, mCursorRow, mColumns, linesToInsert); - } - break; - - case 'M': // Delete Lines - { - int linesAfterCursor = mBottomMargin - mCursorRow; - int linesToDelete = Math.min(getArg0(1), linesAfterCursor); - int linesToMove = linesAfterCursor - linesToDelete; - mScreen.blockCopy(0, mCursorRow + linesToDelete, mColumns, - linesToMove, 0, mCursorRow); - blockClear(0, mCursorRow + linesToMove, mColumns, linesToDelete); - } - break; - - case 'P': // Delete Characters - { - int charsAfterCursor = mColumns - mCursorCol; - int charsToDelete = Math.min(getArg0(1), charsAfterCursor); - int charsToMove = charsAfterCursor - charsToDelete; - mScreen.blockCopy(mCursorCol + charsToDelete, mCursorRow, - charsToMove, 1, mCursorCol, mCursorRow); - blockClear(mCursorCol + charsToMove, mCursorRow, charsToDelete); - } - break; - - case 'T': // Mouse tracking - unimplementedSequence(b); - break; - - case '?': // Esc [ ? -- start of a private mode set - continueSequence(ESC_LEFT_SQUARE_BRACKET_QUESTION_MARK); - break; - - case 'c': // Send device attributes - sendDeviceAttributes(); - break; - - case 'd': // ESC [ Pn d - Vert Position Absolute - setCursorRow(Math.min(Math.max(1, getArg0(1)), mRows) - 1); - break; - - case 'f': // Horizontal and Vertical Position - setHorizontalVerticalPosition(); - break; - - case 'g': // Clear tab stop - switch (getArg0(0)) { - case 0: - mTabStop[mCursorCol] = false; - break; - - case 3: - for (int i = 0; i < mColumns; i++) { - mTabStop[i] = false; - } - break; - - default: - // Specified to have no effect. - break; - } - break; - - case 'h': // Set Mode - doSetMode(true); - break; - - case 'l': // Reset Mode - doSetMode(false); - break; - - case 'm': // Esc [ Pn m - character attributes. - selectGraphicRendition(); - break; - - case 'r': // Esc [ Pn ; Pn r - set top and bottom margins - { - // The top margin defaults to 1, the bottom margin - // (unusually for arguments) defaults to mRows. - // - // The escape sequence numbers top 1..23, but we - // number top 0..22. - // The escape sequence numbers bottom 2..24, and - // so do we (because we use a zero based numbering - // scheme, but we store the first line below the - // bottom-most scrolling line. - // As a result, we adjust the top line by -1, but - // we leave the bottom line alone. - // - // Also require that top + 2 <= bottom - - int top = Math.max(0, Math.min(getArg0(1) - 1, mRows - 2)); - int bottom = Math.max(top + 2, Math.min(getArg1(mRows), mRows)); - mTopMargin = top; - mBottomMargin = bottom; - - // The cursor is placed in the home position - setCursorRowCol(mTopMargin, 0); - } - break; - - default: - parseArg(b); - break; - } - } - - private void selectGraphicRendition() { - for (int i = 0; i <= mArgIndex; i++) { - int code = mArgs[i]; - if ( code < 0) { - if (mArgIndex > 0) { - continue; - } else { - code = 0; - } - } - - // See http://en.wikipedia.org/wiki/ANSI_escape_code#graphics - - if (code == 0) { // reset - mInverseColors = false; - mForeColor = 7; - mBackColor = 0; - } else if (code == 1) { // bold - mForeColor |= 0x8; - } else if (code == 4) { // underscore - mBackColor |= 0x8; - } else if (code == 7) { // inverse - mInverseColors = true; - } else if (code == 22) { // Normal color or intensity, neither bright, bold nor faint - mForeColor &= 0x7; - } else if (code == 24) { // underline: none - mBackColor &= 0x7; - } else if (code == 27) { // image: positive - mInverseColors = false; - } else if (code >= 30 && code <= 37) { // foreground color - mForeColor = (mForeColor & 0x8) | (code - 30); - } else if (code == 39) { // set default text color - mForeColor = 7; - mBackColor = mBackColor & 0x7; - } else if (code >= 40 && code <= 47) { // background color - mBackColor = (mBackColor & 0x8) | (code - 40); - } else if (code == 49) { // set default background color - mBackColor = mBackColor & 0x8; // color 0, but preserve underscore. - } else { - if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) { - Log.w(Term.LOG_TAG, String.format("SGR unknown code %d", code)); - } - } - } - } - - private void blockClear(int sx, int sy, int w) { - blockClear(sx, sy, w, 1); - } - - private void blockClear(int sx, int sy, int w, int h) { - mScreen.blockSet(sx, sy, w, h, ' ', getForeColor(), getBackColor()); - } - - private int getForeColor() { - return mInverseColors ? - ((mBackColor & 0x7) | (mForeColor & 0x8)) : mForeColor; - } - - private int getBackColor() { - return mInverseColors ? - ((mForeColor & 0x7) | (mBackColor & 0x8)) : mBackColor; - } - - private void doSetMode(boolean newValue) { - int modeBit = getArg0(0); - switch (modeBit) { - case 4: - mInsertMode = newValue; - break; - - case 20: - mAutomaticNewlineMode = newValue; - break; - - default: - unknownParameter(modeBit); - break; - } - } - - private void setHorizontalVerticalPosition() { - - // Parameters are Row ; Column - - setCursorPosition(getArg1(1) - 1, getArg0(1) - 1); - } - - private void setCursorPosition(int x, int y) { - int effectiveTopMargin = 0; - int effectiveBottomMargin = mRows; - if ((mDecFlags & K_ORIGIN_MODE_MASK) != 0) { - effectiveTopMargin = mTopMargin; - effectiveBottomMargin = mBottomMargin; - } - int newRow = - Math.max(effectiveTopMargin, Math.min(effectiveTopMargin + y, - effectiveBottomMargin - 1)); - int newCol = Math.max(0, Math.min(x, mColumns - 1)); - setCursorRowCol(newRow, newCol); - } - - private void sendDeviceAttributes() { - // This identifies us as a DEC vt100 with advanced - // video options. This is what the xterm terminal - // emulator sends. - byte[] attributes = - { - /* VT100 */ - (byte) 27, (byte) '[', (byte) '?', (byte) '1', - (byte) ';', (byte) '2', (byte) 'c' - - /* VT220 - (byte) 27, (byte) '[', (byte) '?', (byte) '6', - (byte) '0', (byte) ';', - (byte) '1', (byte) ';', - (byte) '2', (byte) ';', - (byte) '6', (byte) ';', - (byte) '8', (byte) ';', - (byte) '9', (byte) ';', - (byte) '1', (byte) '5', (byte) ';', - (byte) 'c' - */ - }; - - write(attributes); - } - - /** - * Send data to the shell process - * @param data - */ - private void write(byte[] data) { - try { - mTermOut.write(data); - mTermOut.flush(); - } catch (IOException e) { - // Ignore exception - // We don't really care if the receiver isn't listening. - // We just make a best effort to answer the query. - } - } - - private void scroll() { - //System.out.println("Scroll(): mTopMargin " + mTopMargin + " mBottomMargin " + mBottomMargin); - mScrollCounter ++; - mScreen.scroll(mTopMargin, mBottomMargin, - getForeColor(), getBackColor()); - } - - /** - * Process the next ASCII character of a parameter. - * - * @param b The next ASCII character of the paramater sequence. - */ - private void parseArg(byte b) { - if (b >= '0' && b <= '9') { - if (mArgIndex < mArgs.length) { - int oldValue = mArgs[mArgIndex]; - int thisDigit = b - '0'; - int value; - if (oldValue >= 0) { - value = oldValue * 10 + thisDigit; - } else { - value = thisDigit; - } - mArgs[mArgIndex] = value; - } - continueSequence(); - } else if (b == ';') { - if (mArgIndex < mArgs.length) { - mArgIndex++; - } - continueSequence(); - } else { - unknownSequence(b); - } - } - - private int getArg0(int defaultValue) { - return getArg(0, defaultValue); - } - - private int getArg1(int defaultValue) { - return getArg(1, defaultValue); - } - - private int getArg(int index, int defaultValue) { - int result = mArgs[index]; - if (result < 0) { - result = defaultValue; - } - return result; - } - - private void unimplementedSequence(byte b) { - if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) { - logError("unimplemented", b); - } - finishSequence(); - } - - private void unknownSequence(byte b) { - if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) { - logError("unknown", b); - } - finishSequence(); - } - - private void unknownParameter(int parameter) { - if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) { - StringBuilder buf = new StringBuilder(); - buf.append("Unknown parameter"); - buf.append(parameter); - logError(buf.toString()); - } - } - - private void logError(String errorType, byte b) { - if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) { - StringBuilder buf = new StringBuilder(); - buf.append(errorType); - buf.append(" sequence "); - buf.append(" EscapeState: "); - buf.append(mEscapeState); - buf.append(" char: '"); - buf.append((char) b); - buf.append("' ("); - buf.append(b); - buf.append(")"); - boolean firstArg = true; - for (int i = 0; i <= mArgIndex; i++) { - int value = mArgs[i]; - if (value >= 0) { - if (firstArg) { - firstArg = false; - buf.append("args = "); - } - buf.append(String.format("%d; ", value)); - } - } - logError(buf.toString()); - } - } - - private void logError(String error) { - if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) { - Log.e(Term.LOG_TAG, error); - } - finishSequence(); - } - - private void finishSequence() { - mEscapeState = ESC_NONE; - } - - private boolean autoWrapEnabled() { - // Always enable auto wrap, because it's useful on a small screen - return true; - // return (mDecFlags & K_WRAPAROUND_MODE_MASK) != 0; - } - - /** - * Send an ASCII character to the screen. - * - * @param b the ASCII character to display. - */ - private void emit(byte b) { - boolean autoWrap = autoWrapEnabled(); - - if (autoWrap) { - if (mCursorCol == mColumns - 1 && mAboutToAutoWrap) { - mScreen.setLineWrap(mCursorRow); - mCursorCol = 0; - if (mCursorRow + 1 < mBottomMargin) { - mCursorRow++; - } else { - scroll(); - } - } - } - - if (mInsertMode) { // Move character to right one space - int destCol = mCursorCol + 1; - if (destCol < mColumns) { - mScreen.blockCopy(mCursorCol, mCursorRow, mColumns - destCol, - 1, destCol, mCursorRow); - } - } - - mScreen.set(mCursorCol, mCursorRow, b, getForeColor(), getBackColor()); - - if (autoWrap) { - mAboutToAutoWrap = (mCursorCol == mColumns - 1); - } - - mCursorCol = Math.min(mCursorCol + 1, mColumns - 1); - } - - private void setCursorRow(int row) { - mCursorRow = row; - mAboutToAutoWrap = false; - } - - private void setCursorCol(int col) { - mCursorCol = col; - mAboutToAutoWrap = false; - } - - private void setCursorRowCol(int row, int col) { - mCursorRow = Math.min(row, mRows-1); - mCursorCol = Math.min(col, mColumns-1); - mAboutToAutoWrap = false; - } - - public int getScrollCounter() { - return mScrollCounter; - } - - public void clearScrollCounter() { - mScrollCounter = 0; - } - - /** - * Reset the terminal emulator to its initial state. - */ - public void reset() { - mCursorRow = 0; - mCursorCol = 0; - mArgIndex = 0; - mContinueSequence = false; - mEscapeState = ESC_NONE; - mSavedCursorRow = 0; - mSavedCursorCol = 0; - mDecFlags = 0; - mSavedDecFlags = 0; - mInsertMode = false; - mAutomaticNewlineMode = false; - mTopMargin = 0; - mBottomMargin = mRows; - mAboutToAutoWrap = false; - mForeColor = 7; - mBackColor = 0; - mInverseColors = false; - mbKeypadApplicationMode = false; - mAlternateCharSet = false; - // mProcessedCharCount is preserved unchanged. - setDefaultTabStops(); - blockClear(0, 0, mColumns, mRows); - } - - public String getTranscriptText() { - return mScreen.getTranscriptText(); - } - public String getSelectedText(int x1, int y1, int x2, int y2) { - return mScreen.getSelectedText(x1, y1, x2, y2); - } -} - -/** - * Text renderer interface - */ - -interface TextRenderer { - int getCharacterWidth(); - int getCharacterHeight(); - void drawTextRun(Canvas canvas, float x, float y, - int lineOffset, char[] text, - int index, int count, boolean cursor, int foreColor, int backColor); -} - -abstract class BaseTextRenderer implements TextRenderer { - protected int[] mForePaint = { - 0xff000000, // Black - 0xffff0000, // Red - 0xff00ff00, // green - 0xffffff00, // yellow - 0xff0000ff, // blue - 0xffff00ff, // magenta - 0xff00ffff, // cyan - 0xffffffff // white -- is overridden by constructor - }; - protected int[] mBackPaint = { - 0xff000000, // Black -- is overridden by constructor - 0xffcc0000, // Red - 0xff00cc00, // green - 0xffcccc00, // yellow - 0xff0000cc, // blue - 0xffff00cc, // magenta - 0xff00cccc, // cyan - 0xffffffff // white - }; - protected final static int mCursorPaint = 0xff808080; - - public BaseTextRenderer(int forePaintColor, int backPaintColor) { - mForePaint[7] = forePaintColor; - mBackPaint[0] = backPaintColor; - - } -} - -class Bitmap4x8FontRenderer extends BaseTextRenderer { - private final static int kCharacterWidth = 4; - private final static int kCharacterHeight = 8; - private Bitmap mFont; - private int mCurrentForeColor; - private int mCurrentBackColor; - private float[] mColorMatrix; - private Paint mPaint; - private static final float BYTE_SCALE = 1.0f / 255.0f; - - public Bitmap4x8FontRenderer(Resources resources, - int forePaintColor, int backPaintColor) { - super(forePaintColor, backPaintColor); - mFont = BitmapFactory.decodeResource(resources, - R.drawable.atari_small); - mPaint = new Paint(); - mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); - } - - public int getCharacterWidth() { - return kCharacterWidth; - } - - public int getCharacterHeight() { - return kCharacterHeight; - } - - public void drawTextRun(Canvas canvas, float x, float y, - int lineOffset, char[] text, int index, int count, - boolean cursor, int foreColor, int backColor) { - setColorMatrix(mForePaint[foreColor & 7], - cursor ? mCursorPaint : mBackPaint[backColor & 7]); - int destX = (int) x + kCharacterWidth * lineOffset; - int destY = (int) y; - Rect srcRect = new Rect(); - Rect destRect = new Rect(); - destRect.top = (destY - kCharacterHeight); - destRect.bottom = destY; - for(int i = 0; i < count; i++) { - char c = text[i + index]; - if ((cursor || (c != 32)) && (c < 128)) { - int cellX = c & 31; - int cellY = (c >> 5) & 3; - int srcX = cellX * kCharacterWidth; - int srcY = cellY * kCharacterHeight; - srcRect.set(srcX, srcY, - srcX + kCharacterWidth, srcY + kCharacterHeight); - destRect.left = destX; - destRect.right = destX + kCharacterWidth; - canvas.drawBitmap(mFont, srcRect, destRect, mPaint); - } - destX += kCharacterWidth; - } - } - - private void setColorMatrix(int foreColor, int backColor) { - if ((foreColor != mCurrentForeColor) - || (backColor != mCurrentBackColor) - || (mColorMatrix == null)) { - mCurrentForeColor = foreColor; - mCurrentBackColor = backColor; - if (mColorMatrix == null) { - mColorMatrix = new float[20]; - mColorMatrix[18] = 1.0f; // Just copy Alpha - } - for (int component = 0; component < 3; component++) { - int rightShift = (2 - component) << 3; - int fore = 0xff & (foreColor >> rightShift); - int back = 0xff & (backColor >> rightShift); - int delta = back - fore; - mColorMatrix[component * 6] = delta * BYTE_SCALE; - mColorMatrix[component * 5 + 4] = fore; - } - mPaint.setColorFilter(new ColorMatrixColorFilter(mColorMatrix)); - } - } -} - -class PaintRenderer extends BaseTextRenderer { - public PaintRenderer(int fontSize, int forePaintColor, int backPaintColor) { - super(forePaintColor, backPaintColor); - mTextPaint = new Paint(); - mTextPaint.setTypeface(Typeface.MONOSPACE); - mTextPaint.setAntiAlias(true); - mTextPaint.setTextSize(fontSize); - - mCharHeight = (int) Math.ceil(mTextPaint.getFontSpacing()); - mCharAscent = (int) Math.ceil(mTextPaint.ascent()); - mCharDescent = mCharHeight + mCharAscent; - mCharWidth = (int) mTextPaint.measureText(EXAMPLE_CHAR, 0, 1); - } - - public void drawTextRun(Canvas canvas, float x, float y, int lineOffset, - char[] text, int index, int count, - boolean cursor, int foreColor, int backColor) { - if (cursor) { - mTextPaint.setColor(mCursorPaint); - } else { - mTextPaint.setColor(mBackPaint[backColor & 0x7]); - } - float left = x + lineOffset * mCharWidth; - canvas.drawRect(left, y + mCharAscent, - left + count * mCharWidth, y + mCharDescent, - mTextPaint); - boolean bold = ( foreColor & 0x8 ) != 0; - boolean underline = (backColor & 0x8) != 0; - if (bold) { - mTextPaint.setFakeBoldText(true); - } - if (underline) { - mTextPaint.setUnderlineText(true); - } - mTextPaint.setColor(mForePaint[foreColor & 0x7]); - canvas.drawText(text, index, count, left, y, mTextPaint); - if (bold) { - mTextPaint.setFakeBoldText(false); - } - if (underline) { - mTextPaint.setUnderlineText(false); - } - } - - public int getCharacterHeight() { - return mCharHeight; - } - - public int getCharacterWidth() { - return mCharWidth; - } - - - private Paint mTextPaint; - private int mCharWidth; - private int mCharHeight; - private int mCharAscent; - private int mCharDescent; - private static final char[] EXAMPLE_CHAR = {'X'}; - } - -/** - * A multi-thread-safe produce-consumer byte array. - * Only allows one producer and one consumer. - */ - -class ByteQueue { - public ByteQueue(int size) { - mBuffer = new byte[size]; - } - - public int getBytesAvailable() { - synchronized(this) { - return mStoredBytes; - } - } - - public int read(byte[] buffer, int offset, int length) - throws InterruptedException { - if (length + offset > buffer.length) { - throw - new IllegalArgumentException("length + offset > buffer.length"); - } - if (length < 0) { - throw - new IllegalArgumentException("length < 0"); - - } - if (length == 0) { - return 0; - } - synchronized(this) { - while (mStoredBytes == 0) { - wait(); - } - int totalRead = 0; - int bufferLength = mBuffer.length; - boolean wasFull = bufferLength == mStoredBytes; - while (length > 0 && mStoredBytes > 0) { - int oneRun = Math.min(bufferLength - mHead, mStoredBytes); - int bytesToCopy = Math.min(length, oneRun); - System.arraycopy(mBuffer, mHead, buffer, offset, bytesToCopy); - mHead += bytesToCopy; - if (mHead >= bufferLength) { - mHead = 0; - } - mStoredBytes -= bytesToCopy; - length -= bytesToCopy; - offset += bytesToCopy; - totalRead += bytesToCopy; - } - if (wasFull) { - notify(); - } - return totalRead; - } - } - - public void write(byte[] buffer, int offset, int length) - throws InterruptedException { - if (length + offset > buffer.length) { - throw - new IllegalArgumentException("length + offset > buffer.length"); - } - if (length < 0) { - throw - new IllegalArgumentException("length < 0"); - - } - if (length == 0) { - return; - } - synchronized(this) { - int bufferLength = mBuffer.length; - boolean wasEmpty = mStoredBytes == 0; - while (length > 0) { - while(bufferLength == mStoredBytes) { - wait(); - } - int tail = mHead + mStoredBytes; - int oneRun; - if (tail >= bufferLength) { - tail = tail - bufferLength; - oneRun = mHead - tail; - } else { - oneRun = bufferLength - tail; - } - int bytesToCopy = Math.min(oneRun, length); - System.arraycopy(buffer, offset, mBuffer, tail, bytesToCopy); - offset += bytesToCopy; - mStoredBytes += bytesToCopy; - length -= bytesToCopy; - } - if (wasEmpty) { - notify(); - } - } - } - - private byte[] mBuffer; - private int mHead; - private int mStoredBytes; -} -/** - * A view on a transcript and a terminal emulator. Displays the text of the - * transcript and the current cursor position of the terminal emulator. - */ -class EmulatorView extends View implements GestureDetector.OnGestureListener { - - private final String TAG = "EmulatorView"; - private final boolean LOG_KEY_EVENTS = Term.DEBUG && false; - - private Term mTerm; - - /** - * We defer some initialization until we have been layed out in the view - * hierarchy. The boolean tracks when we know what our size is. - */ - private boolean mKnownSize; - - private int mVisibleWidth; - private int mVisibleHeight; - private Rect mVisibleRect = new Rect(); - - /** - * Our transcript. Contains the screen and the transcript. - */ - private TranscriptScreen mTranscriptScreen; - - /** - * Number of rows in the transcript. - */ - private static final int TRANSCRIPT_ROWS = 10000; - - /** - * Total width of each character, in pixels - */ - private int mCharacterWidth; - - /** - * Total height of each character, in pixels - */ - private int mCharacterHeight; - - /** - * Used to render text - */ - private TextRenderer mTextRenderer; - - /** - * Text size. Zero means 4 x 8 font. - */ - private int mTextSize; - - private int mCursorStyle; - private int mCursorBlink; - - /** - * Foreground color. - */ - private int mForeground; - - /** - * Background color. - */ - private int mBackground; - - /** - * Used to paint the cursor - */ - private Paint mCursorPaint; - - private Paint mBackgroundPaint; - - private boolean mUseCookedIme; - - /** - * Our terminal emulator. We use this to get the current cursor position. - */ - private TerminalEmulator mEmulator; - - /** - * The number of rows of text to display. - */ - private int mRows; - - /** - * The number of columns of text to display. - */ - private int mColumns; - - /** - * The number of columns that are visible on the display. - */ - - private int mVisibleColumns; - - /** - * The top row of text to display. Ranges from -activeTranscriptRows to 0 - */ - private int mTopRow; - - private int mLeftColumn; - - private FileDescriptor mTermFd; - /** - * Used to receive data from the remote process. - */ - private FileInputStream mTermIn; - - private FileOutputStream mTermOut; - - private ByteQueue mByteQueue; - - /** - * Used to temporarily hold data received from the remote process. Allocated - * once and used permanently to minimize heap thrashing. - */ - private byte[] mReceiveBuffer; - - /** - * Our private message id, which we use to receive new input from the - * remote process. - */ - private static final int UPDATE = 1; - - private static final int SCREEN_CHECK_PERIOD = 1000; - private static final int CURSOR_BLINK_PERIOD = 1000; - - private boolean mCursorVisible = true; - - private boolean mIsSelectingText = false; - - - private float mScaledDensity; - private static final int SELECT_TEXT_OFFSET_Y = -40; - private int mSelXAnchor = -1; - private int mSelYAnchor = -1; - private int mSelX1 = -1; - private int mSelY1 = -1; - private int mSelX2 = -1; - private int mSelY2 = -1; - - /** - * Used to poll if the view has changed size. Wish there was a better way to do this. - */ - private Runnable mCheckSize = new Runnable() { - - public void run() { - updateSize(false); - mHandler.postDelayed(this, SCREEN_CHECK_PERIOD); - } - }; - - private Runnable mBlinkCursor = new Runnable() { - public void run() { - if (mCursorBlink != 0) { - mCursorVisible = ! mCursorVisible; - mHandler.postDelayed(this, CURSOR_BLINK_PERIOD); - } else { - mCursorVisible = true; - } - // Perhaps just invalidate the character with the cursor. - invalidate(); - } - }; - - /** - * Thread that polls for input from the remote process - */ - - private Thread mPollingThread; - - private GestureDetector mGestureDetector; - private float mScrollRemainder; - private TermKeyListener mKeyListener; - - private String mImeBuffer = ""; - - /** - * Our message handler class. Implements a periodic callback. - */ - private final Handler mHandler = new Handler() { - /** - * Handle the callback message. Call our enclosing class's update - * method. - * - * @param msg The callback message. - */ - @Override - public void handleMessage(Message msg) { - if (msg.what == UPDATE) { - update(); - } - } - }; - - public EmulatorView(Context context) { - super(context); - commonConstructor(); - } - - public void setScaledDensity(float scaledDensity) { - mScaledDensity = scaledDensity; - } - - public void onResume() { - updateSize(false); - mHandler.postDelayed(mCheckSize, SCREEN_CHECK_PERIOD); - if (mCursorBlink != 0) { - mHandler.postDelayed(mBlinkCursor, CURSOR_BLINK_PERIOD); - } - } - - public void onPause() { - mHandler.removeCallbacks(mCheckSize); - if (mCursorBlink != 0) { - mHandler.removeCallbacks(mBlinkCursor); - } - } - - public void register(Term term, TermKeyListener listener) { - mTerm = term; - mKeyListener = listener; - } - - public void setColors(int foreground, int background) { - mForeground = foreground; - mBackground = background; - updateText(); - } - - public String getTranscriptText() { - return mEmulator.getTranscriptText(); - } - - public void resetTerminal() { - mEmulator.reset(); - invalidate(); - } - - @Override - public boolean onCheckIsTextEditor() { - return true; - } - - @Override - public InputConnection onCreateInputConnection(EditorInfo outAttrs) { - outAttrs.inputType = mUseCookedIme ? - EditorInfo.TYPE_CLASS_TEXT : - EditorInfo.TYPE_NULL; - return new InputConnection() { - private boolean mInBatchEdit; - /** - * Used to handle composing text requests - */ - private int mCursor; - private int mComposingTextStart; - private int mComposingTextEnd; - private int mSelectedTextStart; - private int mSelectedTextEnd; - - private void sendChar(int c) { - try { - mapAndSend(c); - } catch (IOException ex) { - - } - } - - private void sendText(CharSequence text) { - int n = text.length(); - try { - for(int i = 0; i < n; i++) { - char c = text.charAt(i); - mapAndSend(c); - } - mTermOut.flush(); - } catch (IOException e) { - Log.e(TAG, "error writing ", e); - } - } - - private void mapAndSend(int c) throws IOException { - int result = mKeyListener.mapControlChar(c); - if (result < TermKeyListener.KEYCODE_OFFSET) { - mTermOut.write(result); - } else { - mKeyListener.handleKeyCode(result - TermKeyListener.KEYCODE_OFFSET, mTermOut, getKeypadApplicationMode()); - } - } - - public boolean beginBatchEdit() { - if (Term.LOG_IME) { - Log.w(TAG, "beginBatchEdit"); - } - setImeBuffer(""); - mCursor = 0; - mComposingTextStart = 0; - mComposingTextEnd = 0; - mInBatchEdit = true; - return true; - } - - public boolean clearMetaKeyStates(int arg0) { - if (Term.LOG_IME) { - Log.w(TAG, "clearMetaKeyStates " + arg0); - } - return false; - } - - public boolean commitCompletion(CompletionInfo arg0) { - if (Term.LOG_IME) { - Log.w(TAG, "commitCompletion " + arg0); - } - return false; - } - - public boolean endBatchEdit() { - if (Term.LOG_IME) { - Log.w(TAG, "endBatchEdit"); - } - mInBatchEdit = false; - return true; - } - - public boolean finishComposingText() { - if (Term.LOG_IME) { - Log.w(TAG, "finishComposingText"); - } - sendText(mImeBuffer); - setImeBuffer(""); - mComposingTextStart = 0; - mComposingTextEnd = 0; - mCursor = 0; - return true; - } - - public int getCursorCapsMode(int arg0) { - if (Term.LOG_IME) { - Log.w(TAG, "getCursorCapsMode(" + arg0 + ")"); - } - return 0; - } - - public ExtractedText getExtractedText(ExtractedTextRequest arg0, - int arg1) { - if (Term.LOG_IME) { - Log.w(TAG, "getExtractedText" + arg0 + "," + arg1); - } - return null; - } - - public CharSequence getTextAfterCursor(int n, int flags) { - if (Term.LOG_IME) { - Log.w(TAG, "getTextAfterCursor(" + n + "," + flags + ")"); - } - int len = Math.min(n, mImeBuffer.length() - mCursor); - if (len <= 0 || mCursor < 0 || mCursor >= mImeBuffer.length()) { - return ""; - } - return mImeBuffer.substring(mCursor, mCursor + len); - } - - public CharSequence getTextBeforeCursor(int n, int flags) { - if (Term.LOG_IME) { - Log.w(TAG, "getTextBeforeCursor(" + n + "," + flags + ")"); - } - int len = Math.min(n, mCursor); - if (len <= 0 || mCursor < 0 || mCursor >= mImeBuffer.length()) { - return ""; - } - return mImeBuffer.substring(mCursor-len, mCursor); - } - - public boolean performContextMenuAction(int arg0) { - if (Term.LOG_IME) { - Log.w(TAG, "performContextMenuAction" + arg0); - } - return true; - } - - public boolean performPrivateCommand(String arg0, Bundle arg1) { - if (Term.LOG_IME) { - Log.w(TAG, "performPrivateCommand" + arg0 + "," + arg1); - } - return true; - } - - public boolean reportFullscreenMode(boolean arg0) { - if (Term.LOG_IME) { - Log.w(TAG, "reportFullscreenMode" + arg0); - } - return true; - } - - public boolean commitCorrection (CorrectionInfo correctionInfo) { - if (Term.LOG_IME) { - Log.w(TAG, "commitCorrection"); - } - return true; - } - - public boolean commitText(CharSequence text, int newCursorPosition) { - if (Term.LOG_IME) { - Log.w(TAG, "commitText(\"" + text + "\", " + newCursorPosition + ")"); - } - clearComposingText(); - sendText(text); - setImeBuffer(""); - mCursor = 0; - return true; - } - - private void clearComposingText() { - setImeBuffer(mImeBuffer.substring(0, mComposingTextStart) + - mImeBuffer.substring(mComposingTextEnd)); - if (mCursor < mComposingTextStart) { - // do nothing - } else if (mCursor < mComposingTextEnd) { - mCursor = mComposingTextStart; - } else { - mCursor -= mComposingTextEnd - mComposingTextStart; - } - mComposingTextEnd = mComposingTextStart = 0; - } - - public boolean deleteSurroundingText(int leftLength, int rightLength) { - if (Term.LOG_IME) { - Log.w(TAG, "deleteSurroundingText(" + leftLength + - "," + rightLength + ")"); - } - if (leftLength > 0) { - for (int i = 0; i < leftLength; i++) { - sendKeyEvent( - new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)); - } - } else if ((leftLength == 0) && (rightLength == 0)) { - // Delete key held down / repeating - sendKeyEvent( - new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)); - } - // TODO: handle forward deletes. - return true; - } - - public boolean performEditorAction(int actionCode) { - if (Term.LOG_IME) { - Log.w(TAG, "performEditorAction(" + actionCode + ")"); - } - if (actionCode == EditorInfo.IME_ACTION_UNSPECIFIED) { - // The "return" key has been pressed on the IME. - sendText("\n"); - } - return true; - } - - public boolean sendKeyEvent(KeyEvent event) { - if (Term.LOG_IME) { - Log.w(TAG, "sendKeyEvent(" + event + ")"); - } - // Some keys are sent here rather than to commitText. - // In particular, del and the digit keys are sent here. - // (And I have reports that the HTC Magic also sends Return here.) - // As a bit of defensive programming, handle every key. - dispatchKeyEvent(event); - return true; - } - - public boolean setComposingText(CharSequence text, int newCursorPosition) { - if (Term.LOG_IME) { - Log.w(TAG, "setComposingText(\"" + text + "\", " + newCursorPosition + ")"); - } - setImeBuffer(mImeBuffer.substring(0, mComposingTextStart) + - text + mImeBuffer.substring(mComposingTextEnd)); - mComposingTextEnd = mComposingTextStart + text.length(); - mCursor = newCursorPosition > 0 ? mComposingTextEnd + newCursorPosition - 1 - : mComposingTextStart - newCursorPosition; - return true; - } - - public boolean setSelection(int start, int end) { - if (Term.LOG_IME) { - Log.w(TAG, "setSelection" + start + "," + end); - } - int length = mImeBuffer.length(); - if (start == end && start > 0 && start < length) { - mSelectedTextStart = mSelectedTextEnd = 0; - mCursor = start; - } else if (start < end && start > 0 && end < length) { - mSelectedTextStart = start; - mSelectedTextEnd = end; - mCursor = start; - } - return true; - } - - public boolean setComposingRegion(int start, int end) { - if (Term.LOG_IME) { - Log.w(TAG, "setComposingRegion " + start + "," + end); - } - if (start < end && start > 0 && end < mImeBuffer.length()) { - clearComposingText(); - mComposingTextStart = start; - mComposingTextEnd = end; - } - return true; - } - - public CharSequence getSelectedText(int flags) { - if (Term.LOG_IME) { - Log.w(TAG, "getSelectedText " + flags); - } - return mImeBuffer.substring(mSelectedTextStart, mSelectedTextEnd+1); - } - - }; - } - - private void setImeBuffer(String buffer) { - if (!buffer.equals(mImeBuffer)) { - invalidate(); - } - mImeBuffer = buffer; - } - - public boolean getKeypadApplicationMode() { - return mEmulator.getKeypadApplicationMode(); - } - - public EmulatorView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public EmulatorView(Context context, AttributeSet attrs, - int defStyle) { - super(context, attrs, defStyle); - // TypedArray a = - // context.obtainStyledAttributes(android.R.styleable.View); - // initializeScrollbars(a); - // a.recycle(); - commonConstructor(); - } - - private void commonConstructor() { - mTextRenderer = null; - mCursorPaint = new Paint(); - mCursorPaint.setARGB(255,128,128,128); - mBackgroundPaint = new Paint(); - mTopRow = 0; - mLeftColumn = 0; - mGestureDetector = new GestureDetector(this); - // mGestureDetector.setIsLongpressEnabled(false); - setVerticalScrollBarEnabled(true); - } - - @Override - protected int computeVerticalScrollRange() { - return mTranscriptScreen.getActiveRows(); - } - - @Override - protected int computeVerticalScrollExtent() { - return mRows; - } - - @Override - protected int computeVerticalScrollOffset() { - return mTranscriptScreen.getActiveRows() + mTopRow - mRows; - } - - /** - * Call this to initialize the view. - * - * @param termFd the file descriptor - * @param termOut the output stream for the pseudo-teletype - */ - public void initialize(FileDescriptor termFd, FileOutputStream termOut) { - mTermOut = termOut; - mTermFd = termFd; - mTextSize = 10; - mForeground = Term.WHITE; - mBackground = Term.BLACK; - updateText(); - mTermIn = new FileInputStream(mTermFd); - mReceiveBuffer = new byte[4 * 1024]; - mByteQueue = new ByteQueue(4 * 1024); - } - - /** - * Accept a sequence of bytes (typically from the pseudo-tty) and process - * them. - * - * @param buffer a byte array containing bytes to be processed - * @param base the index of the first byte in the buffer to process - * @param length the number of bytes to process - */ - public void append(byte[] buffer, int base, int length) { - mEmulator.append(buffer, base, length); - if ( mIsSelectingText ) { - int rowShift = mEmulator.getScrollCounter(); - mSelY1 -= rowShift; - mSelY2 -= rowShift; - mSelYAnchor -= rowShift; - } - mEmulator.clearScrollCounter(); - ensureCursorVisible(); - invalidate(); - } - - /** - * Page the terminal view (scroll it up or down by delta screenfulls.) - * - * @param delta the number of screens to scroll. Positive means scroll down, - * negative means scroll up. - */ - public void page(int delta) { - mTopRow = - Math.min(0, Math.max(-(mTranscriptScreen - .getActiveTranscriptRows()), mTopRow + mRows * delta)); - invalidate(); - } - - /** - * Page the terminal view horizontally. - * - * @param deltaColumns the number of columns to scroll. Positive scrolls to - * the right. - */ - public void pageHorizontal(int deltaColumns) { - mLeftColumn = - Math.max(0, Math.min(mLeftColumn + deltaColumns, mColumns - - mVisibleColumns)); - invalidate(); - } - - /** - * Sets the text size, which in turn sets the number of rows and columns - * - * @param fontSize the new font size, in pixels. - */ - public void setTextSize(int fontSize) { - mTextSize = fontSize; - updateText(); - } - - public void setCursorStyle(int style, int blink) { - mCursorStyle = style; - if (blink != 0 && mCursorBlink == 0) { - mHandler.postDelayed(mBlinkCursor, CURSOR_BLINK_PERIOD); - } else if (blink == 0 && mCursorBlink != 0) { - mHandler.removeCallbacks(mBlinkCursor); - } - mCursorBlink = blink; - } - - public void setUseCookedIME(boolean useCookedIME) { - mUseCookedIme = useCookedIME; - } - - // Begin GestureDetector.OnGestureListener methods - - public boolean onSingleTapUp(MotionEvent e) { - return true; - } - - public void onLongPress(MotionEvent e) { - showContextMenu(); - } - - public boolean onScroll(MotionEvent e1, MotionEvent e2, - float distanceX, float distanceY) { - distanceY += mScrollRemainder; - int deltaRows = (int) (distanceY / mCharacterHeight); - mScrollRemainder = distanceY - deltaRows * mCharacterHeight; - mTopRow = - Math.min(0, Math.max(-(mTranscriptScreen - .getActiveTranscriptRows()), mTopRow + deltaRows)); - invalidate(); - - return true; - } - - public void onSingleTapConfirmed(MotionEvent e) { - } - - public boolean onJumpTapDown(MotionEvent e1, MotionEvent e2) { - // Scroll to bottom - mTopRow = 0; - invalidate(); - return true; - } - - public boolean onJumpTapUp(MotionEvent e1, MotionEvent e2) { - // Scroll to top - mTopRow = -mTranscriptScreen.getActiveTranscriptRows(); - invalidate(); - return true; - } - - public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, - float velocityY) { - // TODO: add animation man's (non animated) fling - mScrollRemainder = 0.0f; - onScroll(e1, e2, 2 * velocityX, -2 * velocityY); - return true; - } - - public void onShowPress(MotionEvent e) { - } - - public boolean onDown(MotionEvent e) { - mScrollRemainder = 0.0f; - return true; - } - - // End GestureDetector.OnGestureListener methods - - @Override public boolean onTouchEvent(MotionEvent ev) { - if (mIsSelectingText) { - return onTouchEventWhileSelectingText(ev); - } else { - return mGestureDetector.onTouchEvent(ev); - } - } - - private boolean onTouchEventWhileSelectingText(MotionEvent ev) { - int action = ev.getAction(); - int cx = (int)(ev.getX() / mCharacterWidth); - int cy = Math.max(0, - (int)((ev.getY() + SELECT_TEXT_OFFSET_Y * mScaledDensity) - / mCharacterHeight) + mTopRow); - switch (action) { - case MotionEvent.ACTION_DOWN: - mSelXAnchor = cx; - mSelYAnchor = cy; - mSelX1 = cx; - mSelY1 = cy; - mSelX2 = mSelX1; - mSelY2 = mSelY1; - break; - case MotionEvent.ACTION_MOVE: - case MotionEvent.ACTION_UP: - int minx = Math.min(mSelXAnchor, cx); - int maxx = Math.max(mSelXAnchor, cx); - int miny = Math.min(mSelYAnchor, cy); - int maxy = Math.max(mSelYAnchor, cy); - mSelX1 = minx; - mSelY1 = miny; - mSelX2 = maxx; - mSelY2 = maxy; - if (action == MotionEvent.ACTION_UP) { - ClipboardManager clip = (ClipboardManager) - getContext().getApplicationContext() - .getSystemService(Context.CLIPBOARD_SERVICE); - clip.setText(getSelectedText().trim()); - toggleSelectingText(); - } - invalidate(); - break; - default: - toggleSelectingText(); - invalidate(); - break; - } - return true; - } - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - if (LOG_KEY_EVENTS) { - Log.w(TAG, "onKeyDown " + keyCode); - } - if (handleControlKey(keyCode, true)) { - return true; - } else if (handleFnKey(keyCode, true)) { - return true; - } else if (isSystemKey(keyCode, event)) { - // Don't intercept the system keys - return super.onKeyDown(keyCode, event); - } - - // Translate the keyCode into an ASCII character. - - try { - mKeyListener.keyDown(keyCode, event, mTermOut, - getKeypadApplicationMode()); - } catch (IOException e) { - // Ignore I/O exceptions - } - return true; - } - - @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - if (LOG_KEY_EVENTS) { - Log.w(TAG, "onKeyUp " + keyCode); - } - if (handleControlKey(keyCode, false)) { - return true; - } else if (handleFnKey(keyCode, false)) { - return true; - } else if (isSystemKey(keyCode, event)) { - // Don't intercept the system keys - return super.onKeyUp(keyCode, event); - } - - mKeyListener.keyUp(keyCode); - return true; - } - - - private boolean handleControlKey(int keyCode, boolean down) { - if (keyCode == mTerm.getControlKeyCode()) { - if (LOG_KEY_EVENTS) { - Log.w(TAG, "handleControlKey " + keyCode); - } - mKeyListener.handleControlKey(down); - return true; - } - return false; - } - - private boolean handleFnKey(int keyCode, boolean down) { - if (keyCode == mTerm.getFnKeyCode()) { - if (LOG_KEY_EVENTS) { - Log.w(TAG, "handleFnKey " + keyCode); - } - mKeyListener.handleFnKey(down); - return true; - } - return false; - } - - private boolean isSystemKey(int keyCode, KeyEvent event) { - return event.isSystem(); - } - - private void updateText() { - if (mTextSize > 0) { - mTextRenderer = new PaintRenderer(mTextSize, mForeground, - mBackground); - } - else { - mTextRenderer = new Bitmap4x8FontRenderer(getResources(), - mForeground, mBackground); - } - mBackgroundPaint.setColor(mBackground); - mCharacterWidth = mTextRenderer.getCharacterWidth(); - mCharacterHeight = mTextRenderer.getCharacterHeight(); - - updateSize(true); - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - boolean oldKnownSize = mKnownSize; - if (!mKnownSize) { - mKnownSize = true; - } - updateSize(false); - if (!oldKnownSize) { - // Set up a thread to read input from the - // pseudo-teletype: - - mPollingThread = new Thread(new Runnable() { - - public void run() { - try { - while(true) { - int read = mTermIn.read(mBuffer); - mByteQueue.write(mBuffer, 0, read); - mHandler.sendMessage( - mHandler.obtainMessage(UPDATE)); - } - } catch (IOException e) { - } catch (InterruptedException e) { - } - } - private byte[] mBuffer = new byte[4096]; - }); - mPollingThread.setName("Input reader"); - mPollingThread.start(); - } - } - - private void updateSize(int w, int h) { - mColumns = Math.max(1, w / mCharacterWidth); - mRows = Math.max(1, h / mCharacterHeight); - mVisibleColumns = mVisibleWidth / mCharacterWidth; - - // Inform the attached pty of our new size: - Exec.setPtyWindowSize(mTermFd, mRows, mColumns, w, h); - - - if (mTranscriptScreen != null) { - mEmulator.updateSize(mColumns, mRows); - } else { - mTranscriptScreen = - new TranscriptScreen(mColumns, TRANSCRIPT_ROWS, mRows, 0, 7); - mEmulator = - new TerminalEmulator(mTranscriptScreen, mColumns, mRows, - mTermOut); - } - - // Reset our paging: - mTopRow = 0; - mLeftColumn = 0; - - invalidate(); - } - - void updateSize(boolean force) { - if (mKnownSize) { - getWindowVisibleDisplayFrame(mVisibleRect); - int w = mVisibleRect.width(); - int h = mVisibleRect.height(); - // Log.w("Term", "(" + w + ", " + h + ")"); - if (force || w != mVisibleWidth || h != mVisibleHeight) { - mVisibleWidth = w; - mVisibleHeight = h; - updateSize(mVisibleWidth, mVisibleHeight); - } - } - } - - /** - * Look for new input from the ptty, send it to the terminal emulator. - */ - private void update() { - int bytesAvailable = mByteQueue.getBytesAvailable(); - int bytesToRead = Math.min(bytesAvailable, mReceiveBuffer.length); - try { - int bytesRead = mByteQueue.read(mReceiveBuffer, 0, bytesToRead); - append(mReceiveBuffer, 0, bytesRead); - } catch (InterruptedException e) { - } - } - - @Override - protected void onDraw(Canvas canvas) { - updateSize(false); - int w = getWidth(); - int h = getHeight(); - canvas.drawRect(0, 0, w, h, mBackgroundPaint); - float x = -mLeftColumn * mCharacterWidth; - float y = mCharacterHeight; - int endLine = mTopRow + mRows; - int cx = mEmulator.getCursorCol(); - int cy = mEmulator.getCursorRow(); - for (int i = mTopRow; i < endLine; i++) { - int cursorX = -1; - if (i == cy && mCursorVisible) { - cursorX = cx; - } - int selx1 = -1; - int selx2 = -1; - if ( i >= mSelY1 && i <= mSelY2 ) { - if ( i == mSelY1 ) { - selx1 = mSelX1; - } - if ( i == mSelY2 ) { - selx2 = mSelX2; - } else { - selx2 = mColumns; - } - } - mTranscriptScreen.drawText(i, canvas, x, y, mTextRenderer, cursorX, selx1, selx2, mImeBuffer); - y += mCharacterHeight; - } - } - - private void ensureCursorVisible() { - mTopRow = 0; - if (mVisibleColumns > 0) { - int cx = mEmulator.getCursorCol(); - int visibleCursorX = mEmulator.getCursorCol() - mLeftColumn; - if (visibleCursorX < 0) { - mLeftColumn = cx; - } else if (visibleCursorX >= mVisibleColumns) { - mLeftColumn = (cx - mVisibleColumns) + 1; - } - } - } - - public void toggleSelectingText() { - mIsSelectingText = ! mIsSelectingText; - setVerticalScrollBarEnabled( ! mIsSelectingText ); - if ( ! mIsSelectingText ) { - mSelX1 = -1; - mSelY1 = -1; - mSelX2 = -1; - mSelY2 = -1; - } - } - - public boolean getSelectingText() { - return mIsSelectingText; - } - - public String getSelectedText() { - return mEmulator.getSelectedText(mSelX1, mSelY1, mSelX2, mSelY2); - } -} - - -/** - * An ASCII key listener. Supports control characters and escape. Keeps track of - * the current state of the alt, shift, and control keys. - */ -class TermKeyListener { - /** - * Android key codes that are defined in the Android 2.3 API. - * We want to recognize these codes, because they will be sent to our - * app when we run on Android 2.3 systems. - * But we don't want to accidentally use 2.3-specific APIs. - * So we compile against the Android 1.6 APIs, and have a copy of the codes here. - */ - - /** Key code constant: Unknown key code. */ - public static final int KEYCODE_UNKNOWN = 0; - /** Key code constant: Soft Left key. - * Usually situated below the display on phones and used as a multi-function - * feature key for selecting a software defined function shown on the bottom left - * of the display. */ - public static final int KEYCODE_SOFT_LEFT = 1; - /** Key code constant: Soft Right key. - * Usually situated below the display on phones and used as a multi-function - * feature key for selecting a software defined function shown on the bottom right - * of the display. */ - public static final int KEYCODE_SOFT_RIGHT = 2; - /** Key code constant: Home key. - * This key is handled by the framework and is never delivered to applications. */ - public static final int KEYCODE_HOME = 3; - /** Key code constant: Back key. */ - public static final int KEYCODE_BACK = 4; - /** Key code constant: Call key. */ - public static final int KEYCODE_CALL = 5; - /** Key code constant: End Call key. */ - public static final int KEYCODE_ENDCALL = 6; - /** Key code constant: '0' key. */ - public static final int KEYCODE_0 = 7; - /** Key code constant: '1' key. */ - public static final int KEYCODE_1 = 8; - /** Key code constant: '2' key. */ - public static final int KEYCODE_2 = 9; - /** Key code constant: '3' key. */ - public static final int KEYCODE_3 = 10; - /** Key code constant: '4' key. */ - public static final int KEYCODE_4 = 11; - /** Key code constant: '5' key. */ - public static final int KEYCODE_5 = 12; - /** Key code constant: '6' key. */ - public static final int KEYCODE_6 = 13; - /** Key code constant: '7' key. */ - public static final int KEYCODE_7 = 14; - /** Key code constant: '8' key. */ - public static final int KEYCODE_8 = 15; - /** Key code constant: '9' key. */ - public static final int KEYCODE_9 = 16; - /** Key code constant: '*' key. */ - public static final int KEYCODE_STAR = 17; - /** Key code constant: '#' key. */ - public static final int KEYCODE_POUND = 18; - /** Key code constant: Directional Pad Up key. - * May also be synthesized from trackball motions. */ - public static final int KEYCODE_DPAD_UP = 19; - /** Key code constant: Directional Pad Down key. - * May also be synthesized from trackball motions. */ - public static final int KEYCODE_DPAD_DOWN = 20; - /** Key code constant: Directional Pad Left key. - * May also be synthesized from trackball motions. */ - public static final int KEYCODE_DPAD_LEFT = 21; - /** Key code constant: Directional Pad Right key. - * May also be synthesized from trackball motions. */ - public static final int KEYCODE_DPAD_RIGHT = 22; - /** Key code constant: Directional Pad Center key. - * May also be synthesized from trackball motions. */ - public static final int KEYCODE_DPAD_CENTER = 23; - /** Key code constant: Volume Up key. - * Adjusts the speaker volume up. */ - public static final int KEYCODE_VOLUME_UP = 24; - /** Key code constant: Volume Down key. - * Adjusts the speaker volume down. */ - public static final int KEYCODE_VOLUME_DOWN = 25; - /** Key code constant: Power key. */ - public static final int KEYCODE_POWER = 26; - /** Key code constant: Camera key. - * Used to launch a camera application or take pictures. */ - public static final int KEYCODE_CAMERA = 27; - /** Key code constant: Clear key. */ - public static final int KEYCODE_CLEAR = 28; - /** Key code constant: 'A' key. */ - public static final int KEYCODE_A = 29; - /** Key code constant: 'B' key. */ - public static final int KEYCODE_B = 30; - /** Key code constant: 'C' key. */ - public static final int KEYCODE_C = 31; - /** Key code constant: 'D' key. */ - public static final int KEYCODE_D = 32; - /** Key code constant: 'E' key. */ - public static final int KEYCODE_E = 33; - /** Key code constant: 'F' key. */ - public static final int KEYCODE_F = 34; - /** Key code constant: 'G' key. */ - public static final int KEYCODE_G = 35; - /** Key code constant: 'H' key. */ - public static final int KEYCODE_H = 36; - /** Key code constant: 'I' key. */ - public static final int KEYCODE_I = 37; - /** Key code constant: 'J' key. */ - public static final int KEYCODE_J = 38; - /** Key code constant: 'K' key. */ - public static final int KEYCODE_K = 39; - /** Key code constant: 'L' key. */ - public static final int KEYCODE_L = 40; - /** Key code constant: 'M' key. */ - public static final int KEYCODE_M = 41; - /** Key code constant: 'N' key. */ - public static final int KEYCODE_N = 42; - /** Key code constant: 'O' key. */ - public static final int KEYCODE_O = 43; - /** Key code constant: 'P' key. */ - public static final int KEYCODE_P = 44; - /** Key code constant: 'Q' key. */ - public static final int KEYCODE_Q = 45; - /** Key code constant: 'R' key. */ - public static final int KEYCODE_R = 46; - /** Key code constant: 'S' key. */ - public static final int KEYCODE_S = 47; - /** Key code constant: 'T' key. */ - public static final int KEYCODE_T = 48; - /** Key code constant: 'U' key. */ - public static final int KEYCODE_U = 49; - /** Key code constant: 'V' key. */ - public static final int KEYCODE_V = 50; - /** Key code constant: 'W' key. */ - public static final int KEYCODE_W = 51; - /** Key code constant: 'X' key. */ - public static final int KEYCODE_X = 52; - /** Key code constant: 'Y' key. */ - public static final int KEYCODE_Y = 53; - /** Key code constant: 'Z' key. */ - public static final int KEYCODE_Z = 54; - /** Key code constant: ',' key. */ - public static final int KEYCODE_COMMA = 55; - /** Key code constant: '.' key. */ - public static final int KEYCODE_PERIOD = 56; - /** Key code constant: Left Alt modifier key. */ - public static final int KEYCODE_ALT_LEFT = 57; - /** Key code constant: Right Alt modifier key. */ - public static final int KEYCODE_ALT_RIGHT = 58; - /** Key code constant: Left Shift modifier key. */ - public static final int KEYCODE_SHIFT_LEFT = 59; - /** Key code constant: Right Shift modifier key. */ - public static final int KEYCODE_SHIFT_RIGHT = 60; - /** Key code constant: Tab key. */ - public static final int KEYCODE_TAB = 61; - /** Key code constant: Space key. */ - public static final int KEYCODE_SPACE = 62; - /** Key code constant: Symbol modifier key. - * Used to enter alternate symbols. */ - public static final int KEYCODE_SYM = 63; - /** Key code constant: Explorer special function key. - * Used to launch a browser application. */ - public static final int KEYCODE_EXPLORER = 64; - /** Key code constant: Envelope special function key. - * Used to launch a mail application. */ - public static final int KEYCODE_ENVELOPE = 65; - /** Key code constant: Enter key. */ - public static final int KEYCODE_ENTER = 66; - /** Key code constant: Backspace key. - * Deletes characters before the insertion point, unlike {@link #KEYCODE_FORWARD_DEL}. */ - public static final int KEYCODE_DEL = 67; - /** Key code constant: '`' (backtick) key. */ - public static final int KEYCODE_GRAVE = 68; - /** Key code constant: '-'. */ - public static final int KEYCODE_MINUS = 69; - /** Key code constant: '=' key. */ - public static final int KEYCODE_EQUALS = 70; - /** Key code constant: '[' key. */ - public static final int KEYCODE_LEFT_BRACKET = 71; - /** Key code constant: ']' key. */ - public static final int KEYCODE_RIGHT_BRACKET = 72; - /** Key code constant: '\' key. */ - public static final int KEYCODE_BACKSLASH = 73; - /** Key code constant: ';' key. */ - public static final int KEYCODE_SEMICOLON = 74; - /** Key code constant: ''' (apostrophe) key. */ - public static final int KEYCODE_APOSTROPHE = 75; - /** Key code constant: '/' key. */ - public static final int KEYCODE_SLASH = 76; - /** Key code constant: '@' key. */ - public static final int KEYCODE_AT = 77; - /** Key code constant: Number modifier key. - * Used to enter numeric symbols. - * This key is not Num Lock; it is more like {@link #KEYCODE_ALT_LEFT} and is - * interpreted as an ALT key by {@link android.text.method.MetaKeyKeyListener}. */ - public static final int KEYCODE_NUM = 78; - /** Key code constant: Headset Hook key. - * Used to hang up calls and stop media. */ - public static final int KEYCODE_HEADSETHOOK = 79; - /** Key code constant: Camera Focus key. - * Used to focus the camera. */ - public static final int KEYCODE_FOCUS = 80; // *Camera* focus - /** Key code constant: '+' key. */ - public static final int KEYCODE_PLUS = 81; - /** Key code constant: Menu key. */ - public static final int KEYCODE_MENU = 82; - /** Key code constant: Notification key. */ - public static final int KEYCODE_NOTIFICATION = 83; - /** Key code constant: Search key. */ - public static final int KEYCODE_SEARCH = 84; - /** Key code constant: Play/Pause media key. */ - public static final int KEYCODE_MEDIA_PLAY_PAUSE= 85; - /** Key code constant: Stop media key. */ - public static final int KEYCODE_MEDIA_STOP = 86; - /** Key code constant: Play Next media key. */ - public static final int KEYCODE_MEDIA_NEXT = 87; - /** Key code constant: Play Previous media key. */ - public static final int KEYCODE_MEDIA_PREVIOUS = 88; - /** Key code constant: Rewind media key. */ - public static final int KEYCODE_MEDIA_REWIND = 89; - /** Key code constant: Fast Forward media key. */ - public static final int KEYCODE_MEDIA_FAST_FORWARD = 90; - /** Key code constant: Mute key. - * Mutes the microphone, unlike {@link #KEYCODE_VOLUME_MUTE}. */ - public static final int KEYCODE_MUTE = 91; - /** Key code constant: Page Up key. */ - public static final int KEYCODE_PAGE_UP = 92; - /** Key code constant: Page Down key. */ - public static final int KEYCODE_PAGE_DOWN = 93; - /** Key code constant: Picture Symbols modifier key. - * Used to switch symbol sets (Emoji, Kao-moji). */ - public static final int KEYCODE_PICTSYMBOLS = 94; // switch symbol-sets (Emoji,Kao-moji) - /** Key code constant: Switch Charset modifier key. - * Used to switch character sets (Kanji, Katakana). */ - public static final int KEYCODE_SWITCH_CHARSET = 95; // switch char-sets (Kanji,Katakana) - /** Key code constant: A Button key. - * On a game controller, the A button should be either the button labeled A - * or the first button on the upper row of controller buttons. */ - public static final int KEYCODE_BUTTON_A = 96; - /** Key code constant: B Button key. - * On a game controller, the B button should be either the button labeled B - * or the second button on the upper row of controller buttons. */ - public static final int KEYCODE_BUTTON_B = 97; - /** Key code constant: C Button key. - * On a game controller, the C button should be either the button labeled C - * or the third button on the upper row of controller buttons. */ - public static final int KEYCODE_BUTTON_C = 98; - /** Key code constant: X Button key. - * On a game controller, the X button should be either the button labeled X - * or the first button on the lower row of controller buttons. */ - public static final int KEYCODE_BUTTON_X = 99; - /** Key code constant: Y Button key. - * On a game controller, the Y button should be either the button labeled Y - * or the second button on the lower row of controller buttons. */ - public static final int KEYCODE_BUTTON_Y = 100; - /** Key code constant: Z Button key. - * On a game controller, the Z button should be either the button labeled Z - * or the third button on the lower row of controller buttons. */ - public static final int KEYCODE_BUTTON_Z = 101; - /** Key code constant: L1 Button key. - * On a game controller, the L1 button should be either the button labeled L1 (or L) - * or the top left trigger button. */ - public static final int KEYCODE_BUTTON_L1 = 102; - /** Key code constant: R1 Button key. - * On a game controller, the R1 button should be either the button labeled R1 (or R) - * or the top right trigger button. */ - public static final int KEYCODE_BUTTON_R1 = 103; - /** Key code constant: L2 Button key. - * On a game controller, the L2 button should be either the button labeled L2 - * or the bottom left trigger button. */ - public static final int KEYCODE_BUTTON_L2 = 104; - /** Key code constant: R2 Button key. - * On a game controller, the R2 button should be either the button labeled R2 - * or the bottom right trigger button. */ - public static final int KEYCODE_BUTTON_R2 = 105; - /** Key code constant: Left Thumb Button key. - * On a game controller, the left thumb button indicates that the left (or only) - * joystick is pressed. */ - public static final int KEYCODE_BUTTON_THUMBL = 106; - /** Key code constant: Right Thumb Button key. - * On a game controller, the right thumb button indicates that the right - * joystick is pressed. */ - public static final int KEYCODE_BUTTON_THUMBR = 107; - /** Key code constant: Start Button key. - * On a game controller, the button labeled Start. */ - public static final int KEYCODE_BUTTON_START = 108; - /** Key code constant: Select Button key. - * On a game controller, the button labeled Select. */ - public static final int KEYCODE_BUTTON_SELECT = 109; - /** Key code constant: Mode Button key. - * On a game controller, the button labeled Mode. */ - public static final int KEYCODE_BUTTON_MODE = 110; - /** Key code constant: Escape key. */ - public static final int KEYCODE_ESCAPE = 111; - /** Key code constant: Forward Delete key. - * Deletes characters ahead of the insertion point, unlike {@link #KEYCODE_DEL}. */ - public static final int KEYCODE_FORWARD_DEL = 112; - /** Key code constant: Left Control modifier key. */ - public static final int KEYCODE_CTRL_LEFT = 113; - /** Key code constant: Right Control modifier key. */ - public static final int KEYCODE_CTRL_RIGHT = 114; - /** Key code constant: Caps Lock modifier key. */ - public static final int KEYCODE_CAPS_LOCK = 115; - /** Key code constant: Scroll Lock key. */ - public static final int KEYCODE_SCROLL_LOCK = 116; - /** Key code constant: Left Meta modifier key. */ - public static final int KEYCODE_META_LEFT = 117; - /** Key code constant: Right Meta modifier key. */ - public static final int KEYCODE_META_RIGHT = 118; - /** Key code constant: Function modifier key. */ - public static final int KEYCODE_FUNCTION = 119; - /** Key code constant: System Request / Print Screen key. */ - public static final int KEYCODE_SYSRQ = 120; - /** Key code constant: Break / Pause key. */ - public static final int KEYCODE_BREAK = 121; - /** Key code constant: Home Movement key. - * Used for scrolling or moving the cursor around to the start of a line - * or to the top of a list. */ - public static final int KEYCODE_MOVE_HOME = 122; - /** Key code constant: End Movement key. - * Used for scrolling or moving the cursor around to the end of a line - * or to the bottom of a list. */ - public static final int KEYCODE_MOVE_END = 123; - /** Key code constant: Insert key. - * Toggles insert / overwrite edit mode. */ - public static final int KEYCODE_INSERT = 124; - /** Key code constant: Forward key. - * Navigates forward in the history stack. Complement of {@link #KEYCODE_BACK}. */ - public static final int KEYCODE_FORWARD = 125; - /** Key code constant: Play media key. */ - public static final int KEYCODE_MEDIA_PLAY = 126; - /** Key code constant: Pause media key. */ - public static final int KEYCODE_MEDIA_PAUSE = 127; - /** Key code constant: Close media key. - * May be used to close a CD tray, for example. */ - public static final int KEYCODE_MEDIA_CLOSE = 128; - /** Key code constant: Eject media key. - * May be used to eject a CD tray, for example. */ - public static final int KEYCODE_MEDIA_EJECT = 129; - /** Key code constant: Record media key. */ - public static final int KEYCODE_MEDIA_RECORD = 130; - /** Key code constant: F1 key. */ - public static final int KEYCODE_F1 = 131; - /** Key code constant: F2 key. */ - public static final int KEYCODE_F2 = 132; - /** Key code constant: F3 key. */ - public static final int KEYCODE_F3 = 133; - /** Key code constant: F4 key. */ - public static final int KEYCODE_F4 = 134; - /** Key code constant: F5 key. */ - public static final int KEYCODE_F5 = 135; - /** Key code constant: F6 key. */ - public static final int KEYCODE_F6 = 136; - /** Key code constant: F7 key. */ - public static final int KEYCODE_F7 = 137; - /** Key code constant: F8 key. */ - public static final int KEYCODE_F8 = 138; - /** Key code constant: F9 key. */ - public static final int KEYCODE_F9 = 139; - /** Key code constant: F10 key. */ - public static final int KEYCODE_F10 = 140; - /** Key code constant: F11 key. */ - public static final int KEYCODE_F11 = 141; - /** Key code constant: F12 key. */ - public static final int KEYCODE_F12 = 142; - /** Key code constant: Num Lock modifier key. - * This is the Num Lock key; it is different from {@link #KEYCODE_NUM}. - * This key generally modifies the behavior of other keys on the numeric keypad. */ - public static final int KEYCODE_NUM_LOCK = 143; - /** Key code constant: Numeric keypad '0' key. */ - public static final int KEYCODE_NUMPAD_0 = 144; - /** Key code constant: Numeric keypad '1' key. */ - public static final int KEYCODE_NUMPAD_1 = 145; - /** Key code constant: Numeric keypad '2' key. */ - public static final int KEYCODE_NUMPAD_2 = 146; - /** Key code constant: Numeric keypad '3' key. */ - public static final int KEYCODE_NUMPAD_3 = 147; - /** Key code constant: Numeric keypad '4' key. */ - public static final int KEYCODE_NUMPAD_4 = 148; - /** Key code constant: Numeric keypad '5' key. */ - public static final int KEYCODE_NUMPAD_5 = 149; - /** Key code constant: Numeric keypad '6' key. */ - public static final int KEYCODE_NUMPAD_6 = 150; - /** Key code constant: Numeric keypad '7' key. */ - public static final int KEYCODE_NUMPAD_7 = 151; - /** Key code constant: Numeric keypad '8' key. */ - public static final int KEYCODE_NUMPAD_8 = 152; - /** Key code constant: Numeric keypad '9' key. */ - public static final int KEYCODE_NUMPAD_9 = 153; - /** Key code constant: Numeric keypad '/' key (for division). */ - public static final int KEYCODE_NUMPAD_DIVIDE = 154; - /** Key code constant: Numeric keypad '*' key (for multiplication). */ - public static final int KEYCODE_NUMPAD_MULTIPLY = 155; - /** Key code constant: Numeric keypad '-' key (for subtraction). */ - public static final int KEYCODE_NUMPAD_SUBTRACT = 156; - /** Key code constant: Numeric keypad '+' key (for addition). */ - public static final int KEYCODE_NUMPAD_ADD = 157; - /** Key code constant: Numeric keypad '.' key (for decimals or digit grouping). */ - public static final int KEYCODE_NUMPAD_DOT = 158; - /** Key code constant: Numeric keypad ',' key (for decimals or digit grouping). */ - public static final int KEYCODE_NUMPAD_COMMA = 159; - /** Key code constant: Numeric keypad Enter key. */ - public static final int KEYCODE_NUMPAD_ENTER = 160; - /** Key code constant: Numeric keypad '=' key. */ - public static final int KEYCODE_NUMPAD_EQUALS = 161; - /** Key code constant: Numeric keypad '(' key. */ - public static final int KEYCODE_NUMPAD_LEFT_PAREN = 162; - /** Key code constant: Numeric keypad ')' key. */ - public static final int KEYCODE_NUMPAD_RIGHT_PAREN = 163; - /** Key code constant: Volume Mute key. - * Mutes the speaker, unlike {@link #KEYCODE_MUTE}. - * This key should normally be implemented as a toggle such that the first press - * mutes the speaker and the second press restores the original volume. */ - public static final int KEYCODE_VOLUME_MUTE = 164; - /** Key code constant: Info key. - * Common on TV remotes to show additional information related to what is - * currently being viewed. */ - public static final int KEYCODE_INFO = 165; - /** Key code constant: Channel up key. - * On TV remotes, increments the television channel. */ - public static final int KEYCODE_CHANNEL_UP = 166; - /** Key code constant: Channel down key. - * On TV remotes, decrements the television channel. */ - public static final int KEYCODE_CHANNEL_DOWN = 167; - /** Key code constant: Zoom in key. */ - public static final int KEYCODE_ZOOM_IN = 168; - /** Key code constant: Zoom out key. */ - public static final int KEYCODE_ZOOM_OUT = 169; - /** Key code constant: TV key. - * On TV remotes, switches to viewing live TV. */ - public static final int KEYCODE_TV = 170; - /** Key code constant: Window key. - * On TV remotes, toggles picture-in-picture mode or other windowing functions. */ - public static final int KEYCODE_WINDOW = 171; - /** Key code constant: Guide key. - * On TV remotes, shows a programming guide. */ - public static final int KEYCODE_GUIDE = 172; - /** Key code constant: DVR key. - * On some TV remotes, switches to a DVR mode for recorded shows. */ - public static final int KEYCODE_DVR = 173; - /** Key code constant: Bookmark key. - * On some TV remotes, bookmarks content or web pages. */ - public static final int KEYCODE_BOOKMARK = 174; - /** Key code constant: Toggle captions key. - * Switches the mode for closed-captioning text, for example during television shows. */ - public static final int KEYCODE_CAPTIONS = 175; - /** Key code constant: Settings key. - * Starts the system settings activity. */ - public static final int KEYCODE_SETTINGS = 176; - /** Key code constant: TV power key. - * On TV remotes, toggles the power on a television screen. */ - public static final int KEYCODE_TV_POWER = 177; - /** Key code constant: TV input key. - * On TV remotes, switches the input on a television screen. */ - public static final int KEYCODE_TV_INPUT = 178; - /** Key code constant: Set-top-box power key. - * On TV remotes, toggles the power on an external Set-top-box. */ - public static final int KEYCODE_STB_POWER = 179; - /** Key code constant: Set-top-box input key. - * On TV remotes, switches the input mode on an external Set-top-box. */ - public static final int KEYCODE_STB_INPUT = 180; - /** Key code constant: A/V Receiver power key. - * On TV remotes, toggles the power on an external A/V Receiver. */ - public static final int KEYCODE_AVR_POWER = 181; - /** Key code constant: A/V Receiver input key. - * On TV remotes, switches the input mode on an external A/V Receiver. */ - public static final int KEYCODE_AVR_INPUT = 182; - /** Key code constant: Red "programmable" key. - * On TV remotes, acts as a contextual/programmable key. */ - public static final int KEYCODE_PROG_RED = 183; - /** Key code constant: Green "programmable" key. - * On TV remotes, actsas a contextual/programmable key. */ - public static final int KEYCODE_PROG_GREEN = 184; - /** Key code constant: Yellow "programmable" key. - * On TV remotes, acts as a contextual/programmable key. */ - public static final int KEYCODE_PROG_YELLOW = 185; - /** Key code constant: Blue "programmable" key. - * On TV remotes, acts as a contextual/programmable key. */ - public static final int KEYCODE_PROG_BLUE = 186; - - private static final int LAST_KEYCODE = KEYCODE_PROG_BLUE; - - private String[] mKeyCodes = new String[256]; - private String[] mAppKeyCodes = new String[256]; - - private void initKeyCodes() { - mKeyCodes[KEYCODE_DPAD_CENTER] = "\015"; - mKeyCodes[KEYCODE_DPAD_UP] = "\033[A"; - mKeyCodes[KEYCODE_DPAD_DOWN] = "\033[B"; - mKeyCodes[KEYCODE_DPAD_RIGHT] = "\033[C"; - mKeyCodes[KEYCODE_DPAD_LEFT] = "\033[D"; - mKeyCodes[KEYCODE_F1] = "\033[OP"; - mKeyCodes[KEYCODE_F2] = "\033[OQ"; - mKeyCodes[KEYCODE_F3] = "\033[OR"; - mKeyCodes[KEYCODE_F4] = "\033[OS"; - mKeyCodes[KEYCODE_F5] = "\033[15~"; - mKeyCodes[KEYCODE_F6] = "\033[17~"; - mKeyCodes[KEYCODE_F7] = "\033[18~"; - mKeyCodes[KEYCODE_F8] = "\033[19~"; - mKeyCodes[KEYCODE_F9] = "\033[20~"; - mKeyCodes[KEYCODE_F10] = "\033[21~"; - mKeyCodes[KEYCODE_F11] = "\033[23~"; - mKeyCodes[KEYCODE_F12] = "\033[24~"; - mKeyCodes[KEYCODE_SYSRQ] = "\033[32~"; // Sys Request / Print - // Is this Scroll lock? mKeyCodes[Cancel] = "\033[33~"; - mKeyCodes[KEYCODE_BREAK] = "\033[34~"; // Pause/Break - - mKeyCodes[KEYCODE_TAB] = "\011"; - mKeyCodes[KEYCODE_ENTER] = "\015"; - mKeyCodes[KEYCODE_ESCAPE] = "\033"; - - mKeyCodes[KEYCODE_INSERT] = "\033[2~"; - mKeyCodes[KEYCODE_FORWARD_DEL] = "\033[3~"; - mKeyCodes[KEYCODE_MOVE_HOME] = "\033[1~"; - mKeyCodes[KEYCODE_MOVE_END] = "\033[4~"; - mKeyCodes[KEYCODE_PAGE_UP] = "\033[5~"; - mKeyCodes[KEYCODE_PAGE_DOWN] = "\033[6~"; - mKeyCodes[KEYCODE_DEL]= "\177"; - mKeyCodes[KEYCODE_NUM_LOCK] = "\033OP"; - mKeyCodes[KEYCODE_NUMPAD_DIVIDE] = "/"; - mKeyCodes[KEYCODE_NUMPAD_MULTIPLY] = "*"; - mKeyCodes[KEYCODE_NUMPAD_SUBTRACT] = "-"; - mKeyCodes[KEYCODE_NUMPAD_ADD] = "+"; - mKeyCodes[KEYCODE_NUMPAD_ENTER] = "\015"; - mKeyCodes[KEYCODE_NUMPAD_EQUALS] = "="; - mKeyCodes[KEYCODE_NUMPAD_DOT] = "."; - mKeyCodes[KEYCODE_NUMPAD_COMMA] = ","; - mKeyCodes[KEYCODE_NUMPAD_0] = "0"; - mKeyCodes[KEYCODE_NUMPAD_1] = "1"; - mKeyCodes[KEYCODE_NUMPAD_2] = "2"; - mKeyCodes[KEYCODE_NUMPAD_3] = "3"; - mKeyCodes[KEYCODE_NUMPAD_4] = "4"; - mKeyCodes[KEYCODE_NUMPAD_5] = "5"; - mKeyCodes[KEYCODE_NUMPAD_6] = "6"; - mKeyCodes[KEYCODE_NUMPAD_7] = "7"; - mKeyCodes[KEYCODE_NUMPAD_8] = "8"; - mKeyCodes[KEYCODE_NUMPAD_9] = "9"; - - mAppKeyCodes[KEYCODE_DPAD_UP] = "\033OA"; - mAppKeyCodes[KEYCODE_DPAD_DOWN] = "\033OB"; - mAppKeyCodes[KEYCODE_DPAD_RIGHT] = "\033OC"; - mAppKeyCodes[KEYCODE_DPAD_LEFT] = "\033OD"; - mAppKeyCodes[KEYCODE_NUMPAD_DIVIDE] = "\033Oo"; - mAppKeyCodes[KEYCODE_NUMPAD_MULTIPLY] = "\033Oj"; - mAppKeyCodes[KEYCODE_NUMPAD_SUBTRACT] = "\033Om"; - mAppKeyCodes[KEYCODE_NUMPAD_ADD] = "\033Ok"; - mAppKeyCodes[KEYCODE_NUMPAD_ENTER] = "\033OM"; - mAppKeyCodes[KEYCODE_NUMPAD_EQUALS] = "\033OX"; - mAppKeyCodes[KEYCODE_NUMPAD_DOT] = "\033On"; - mAppKeyCodes[KEYCODE_NUMPAD_COMMA] = "\033Ol"; - mAppKeyCodes[KEYCODE_NUMPAD_0] = "\033Op"; - mAppKeyCodes[KEYCODE_NUMPAD_1] = "\033Oq"; - mAppKeyCodes[KEYCODE_NUMPAD_2] = "\033Or"; - mAppKeyCodes[KEYCODE_NUMPAD_3] = "\033Os"; - mAppKeyCodes[KEYCODE_NUMPAD_4] = "\033Ot"; - mAppKeyCodes[KEYCODE_NUMPAD_5] = "\033Ou"; - mAppKeyCodes[KEYCODE_NUMPAD_6] = "\033Ov"; - mAppKeyCodes[KEYCODE_NUMPAD_7] = "\033Ow"; - mAppKeyCodes[KEYCODE_NUMPAD_8] = "\033Ox"; - mAppKeyCodes[KEYCODE_NUMPAD_9] = "\033Oy"; - } - - /** - * The state engine for a modifier key. Can be pressed, released, locked, - * and so on. - * - */ - private class ModifierKey { - - private int mState; - - private static final int UNPRESSED = 0; - - private static final int PRESSED = 1; - - private static final int RELEASED = 2; - - private static final int USED = 3; - - private static final int LOCKED = 4; - - /** - * Construct a modifier key. UNPRESSED by default. - * - */ - public ModifierKey() { - mState = UNPRESSED; - } - - public void onPress() { - switch (mState) { - case PRESSED: - // This is a repeat before use - break; - case RELEASED: - mState = LOCKED; - break; - case USED: - // This is a repeat after use - break; - case LOCKED: - mState = UNPRESSED; - break; - default: - mState = PRESSED; - break; - } - } - - public void onRelease() { - switch (mState) { - case USED: - mState = UNPRESSED; - break; - case PRESSED: - mState = RELEASED; - break; - default: - // Leave state alone - break; - } - } - - public void adjustAfterKeypress() { - switch (mState) { - case PRESSED: - mState = USED; - break; - case RELEASED: - mState = UNPRESSED; - break; - default: - // Leave state alone - break; - } - } - - public boolean isActive() { - return mState != UNPRESSED; - } - } - - private ModifierKey mAltKey = new ModifierKey(); - - private ModifierKey mCapKey = new ModifierKey(); - - private ModifierKey mControlKey = new ModifierKey(); - - private ModifierKey mFnKey = new ModifierKey(); - - private boolean mCapsLock; - - static public final int KEYCODE_OFFSET = 1000; - - /** - * Construct a term key listener. - * - */ - public TermKeyListener() { - initKeyCodes(); - } - - public void handleControlKey(boolean down) { - if (down) { - mControlKey.onPress(); - } else { - mControlKey.onRelease(); - } - } - - public void handleFnKey(boolean down) { - if (down) { - mFnKey.onPress(); - } else { - mFnKey.onRelease(); - } - } - - public int mapControlChar(int ch) { - int result = ch; - if (mControlKey.isActive()) { - // Search is the control key. - if (result >= 'a' && result <= 'z') { - result = (char) (result - 'a' + '\001'); - } else if (result >= 'A' && result <= 'Z') { - result = (char) (result - 'A' + '\001'); - } else if (result == ' ' || result == '2') { - result = 0; - } else if (result == '[' || result == '3') { - result = 27; // ^[ (Esc) - } else if (result == '\\' || result == '4') { - result = 28; - } else if (result == ']' || result == '5') { - result = 29; - } else if (result == '^' || result == '6') { - result = 30; // control-^ - } else if (result == '_' || result == '7') { - result = 31; - } else if (result == '8') { - result = 127; // DEL - } else if (result == '9') { - result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_F11; - } else if (result == '0') { - result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_F12; - } - } else if (mFnKey.isActive()) { - if (result == 'w' || result == 'W') { - result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_UP; - } else if (result == 'a' || result == 'A') { - result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_LEFT; - } else if (result == 's' || result == 'S') { - result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_DOWN; - } else if (result == 'd' || result == 'D') { - result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_RIGHT; - } else if (result == 'p' || result == 'P') { - result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_PAGE_UP; - } else if (result == 'n' || result == 'N') { - result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_PAGE_DOWN; - } else if (result == 't' || result == 'T') { - result = KEYCODE_OFFSET + KeyEvent.KEYCODE_TAB; - } else if (result == 'l' || result == 'L') { - result = '|'; - } else if (result == 'u' || result == 'U') { - result = '_'; - } else if (result == 'e' || result == 'E') { - result = 27; // ^[ (Esc) - } else if (result == '.') { - result = 28; // ^\ - } else if (result > '0' && result <= '9') { - // F1-F9 - result = (char)(result + KEYCODE_OFFSET + TermKeyListener.KEYCODE_F1 - 1); - } else if (result == '0') { - result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_F10; - } else if (result == 'i' || result == 'I') { - result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_INSERT; - } else if (result == 'x' || result == 'X') { - result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_FORWARD_DEL; - } else if (result == 'h' || result == 'H') { - result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_MOVE_HOME; - } else if (result == 'f' || result == 'F') { - result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_MOVE_END; - } - } - - if (result > -1) { - mAltKey.adjustAfterKeypress(); - mCapKey.adjustAfterKeypress(); - mControlKey.adjustAfterKeypress(); - mFnKey.adjustAfterKeypress(); - } - - return result; - } - - /** - * Handle a keyDown event. - * - * @param keyCode the keycode of the keyDown event - * - */ - public void keyDown(int keyCode, KeyEvent event, OutputStream out, boolean appMode) throws IOException { - if (handleKeyCode(keyCode, out, appMode)) { - return; - } - int result = -1; - switch (keyCode) { - case KeyEvent.KEYCODE_ALT_RIGHT: - case KeyEvent.KEYCODE_ALT_LEFT: - mAltKey.onPress(); - break; - - case KeyEvent.KEYCODE_SHIFT_LEFT: - case KeyEvent.KEYCODE_SHIFT_RIGHT: - mCapKey.onPress(); - break; - - case KEYCODE_CTRL_LEFT: - case KEYCODE_CTRL_RIGHT: - mControlKey.onPress(); - break; - - case KEYCODE_CAPS_LOCK: - if (event.getRepeatCount() == 0) { - mCapsLock = !mCapsLock; - } - break; - - default: { - result = event.getUnicodeChar( - (mCapKey.isActive() || mCapsLock ? KeyEvent.META_SHIFT_ON : 0) | - (mAltKey.isActive() ? KeyEvent.META_ALT_ON : 0)); - break; - } - } - - result = mapControlChar(result); - - if (result >= KEYCODE_OFFSET) { - handleKeyCode(result - KEYCODE_OFFSET, out, appMode); - } else if (result >= 0) { - out.write(result); - } - } - - public boolean handleKeyCode(int keyCode, OutputStream out, boolean appMode) throws IOException { - if (keyCode >= 0 && keyCode < mKeyCodes.length) { - String code = null; - if (appMode) { - code = mAppKeyCodes[keyCode]; - } - if (code == null) { - code = mKeyCodes[keyCode]; - } - if (code != null) { - int length = code.length(); - for (int i = 0; i < length; i++) { - out.write(code.charAt(i)); - } - return true; - } - } - return false; - } - - /** - * Handle a keyUp event. - * - * @param keyCode the keyCode of the keyUp event - */ - public void keyUp(int keyCode) { - switch (keyCode) { - case KeyEvent.KEYCODE_ALT_LEFT: - case KeyEvent.KEYCODE_ALT_RIGHT: - mAltKey.onRelease(); - break; - case KeyEvent.KEYCODE_SHIFT_LEFT: - case KeyEvent.KEYCODE_SHIFT_RIGHT: - mCapKey.onRelease(); - break; - - case KEYCODE_CTRL_LEFT: - case KEYCODE_CTRL_RIGHT: - mControlKey.onRelease(); - break; - - default: - // Ignore other keyUps - break; - } - } -} diff --git a/src/jackpal/androidterm/TermService.java b/src/jackpal/androidterm/TermService.java index 00b53e860..060ffad16 100644 --- a/src/jackpal/androidterm/TermService.java +++ b/src/jackpal/androidterm/TermService.java @@ -24,6 +24,8 @@ import android.app.NotificationManager; import android.app.PendingIntent; +import jackpal.androidterm.util.ServiceForegroundCompat; + public class TermService extends Service { /* Parallels the value of START_STICKY on API Level >= 5 */ diff --git a/src/jackpal/androidterm/model/Screen.java b/src/jackpal/androidterm/model/Screen.java new file mode 100644 index 000000000..6ceb45251 --- /dev/null +++ b/src/jackpal/androidterm/model/Screen.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jackpal.androidterm.model; + +/** + * An abstract screen interface. A terminal screen stores lines of text. (The + * reason to abstract it is to allow different implementations, and to hide + * implementation details from clients.) + */ +public interface Screen { + + /** + * Set line wrap flag for a given row. Affects how lines are logically + * wrapped when changing screen size or converting to a transcript. + */ + void setLineWrap(int row); + + /** + * Store byte b into the screen at location (x, y) + * + * @param x X coordinate (also known as column) + * @param y Y coordinate (also known as row) + * @param b ASCII character to store + * @param foreColor the foreground color + * @param backColor the background color + */ + void set(int x, int y, byte b, int foreColor, int backColor); + + /** + * Scroll the screen down one line. To scroll the whole screen of a 24 line + * screen, the arguments would be (0, 24). + * + * @param topMargin First line that is scrolled. + * @param bottomMargin One line after the last line that is scrolled. + */ + void scroll(int topMargin, int bottomMargin, int foreColor, int backColor); + + /** + * Block copy characters from one position in the screen to another. The two + * positions can overlap. All characters of the source and destination must + * be within the bounds of the screen, or else an InvalidParemeterException + * will be thrown. + * + * @param sx source X coordinate + * @param sy source Y coordinate + * @param w width + * @param h height + * @param dx destination X coordinate + * @param dy destination Y coordinate + */ + void blockCopy(int sx, int sy, int w, int h, int dx, int dy); + + /** + * Block set characters. All characters must be within the bounds of the + * screen, or else and InvalidParemeterException will be thrown. Typically + * this is called with a "val" argument of 32 to clear a block of + * characters. + * + * @param sx source X + * @param sy source Y + * @param w width + * @param h height + * @param val value to set. + * @param foreColor the foreground color + * @param backColor the background color + */ + void blockSet(int sx, int sy, int w, int h, int val, int foreColor, int + backColor); + + /** + * Get the contents of the transcript buffer as a text string. + * + * @return the contents of the transcript buffer. + */ + String getTranscriptText(); + + /** + * Get the selected text inside transcript buffer as a text string. + * @param x1 Selection start + * @param y1 Selection start + * @param x2 Selection end + * @param y2 Selection end + * @return the contents of the transcript buffer. + */ + String getSelectedText(int x1, int y1, int x2, int y2); + + /** + * Resize the screen + * @param columns + * @param rows + */ + void resize(int columns, int rows, int foreColor, int backColor); +} diff --git a/src/jackpal/androidterm/model/TextRenderer.java b/src/jackpal/androidterm/model/TextRenderer.java new file mode 100644 index 000000000..f4e0a7b75 --- /dev/null +++ b/src/jackpal/androidterm/model/TextRenderer.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jackpal.androidterm.model; + +import android.graphics.Canvas; + +/** + * Text renderer interface + */ + +public interface TextRenderer { + int getCharacterWidth(); + int getCharacterHeight(); + void drawTextRun(Canvas canvas, float x, float y, + int lineOffset, char[] text, + int index, int count, boolean cursor, int foreColor, int backColor); +} diff --git a/src/jackpal/androidterm/session/TerminalEmulator.java b/src/jackpal/androidterm/session/TerminalEmulator.java new file mode 100644 index 000000000..7b5e559e9 --- /dev/null +++ b/src/jackpal/androidterm/session/TerminalEmulator.java @@ -0,0 +1,1236 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jackpal.androidterm.session; + +import java.io.FileOutputStream; +import java.io.IOException; + +import android.util.Log; + +import jackpal.androidterm.Term; +import jackpal.androidterm.model.Screen; + +/** + * Renders text into a screen. Contains all the terminal-specific knowlege and + * state. Emulates a subset of the X Window System xterm terminal, which in turn + * is an emulator for a subset of the Digital Equipment Corporation vt100 + * terminal. Missing functionality: text attributes (bold, underline, reverse + * video, color) alternate screen cursor key and keypad escape sequences. + */ +public class TerminalEmulator { + + /** + * The cursor row. Numbered 0..mRows-1. + */ + private int mCursorRow; + + /** + * The cursor column. Numbered 0..mColumns-1. + */ + private int mCursorCol; + + /** + * The number of character rows in the terminal screen. + */ + private int mRows; + + /** + * The number of character columns in the terminal screen. + */ + private int mColumns; + + /** + * Used to send data to the remote process. Needed to implement the various + * "report" escape sequences. + */ + private FileOutputStream mTermOut; + + /** + * Stores the characters that appear on the screen of the emulated terminal. + */ + private Screen mScreen; + + /** + * Keeps track of the current argument of the current escape sequence. + * Ranges from 0 to MAX_ESCAPE_PARAMETERS-1. (Typically just 0 or 1.) + */ + private int mArgIndex; + + /** + * The number of parameter arguments. This name comes from the ANSI standard + * for terminal escape codes. + */ + private static final int MAX_ESCAPE_PARAMETERS = 16; + + /** + * Holds the arguments of the current escape sequence. + */ + private int[] mArgs = new int[MAX_ESCAPE_PARAMETERS]; + + // Escape processing states: + + /** + * Escape processing state: Not currently in an escape sequence. + */ + private static final int ESC_NONE = 0; + + /** + * Escape processing state: Have seen an ESC character + */ + private static final int ESC = 1; + + /** + * Escape processing state: Have seen ESC POUND + */ + private static final int ESC_POUND = 2; + + /** + * Escape processing state: Have seen ESC and a character-set-select char + */ + private static final int ESC_SELECT_LEFT_PAREN = 3; + + /** + * Escape processing state: Have seen ESC and a character-set-select char + */ + private static final int ESC_SELECT_RIGHT_PAREN = 4; + + /** + * Escape processing state: ESC [ + */ + private static final int ESC_LEFT_SQUARE_BRACKET = 5; + + /** + * Escape processing state: ESC [ ? + */ + private static final int ESC_LEFT_SQUARE_BRACKET_QUESTION_MARK = 6; + + /** + * True if the current escape sequence should continue, false if the current + * escape sequence should be terminated. Used when parsing a single + * character. + */ + private boolean mContinueSequence; + + /** + * The current state of the escape sequence state machine. + */ + private int mEscapeState; + + /** + * Saved state of the cursor row, Used to implement the save/restore cursor + * position escape sequences. + */ + private int mSavedCursorRow; + + /** + * Saved state of the cursor column, Used to implement the save/restore + * cursor position escape sequences. + */ + private int mSavedCursorCol; + + // DecSet booleans + + /** + * This mask indicates 132-column mode is set. (As opposed to 80-column + * mode.) + */ + private static final int K_132_COLUMN_MODE_MASK = 1 << 3; + + /** + * This mask indicates that origin mode is set. (Cursor addressing is + * relative to the absolute screen size, rather than the currently set top + * and bottom margins.) + */ + private static final int K_ORIGIN_MODE_MASK = 1 << 6; + + /** + * This mask indicates that wraparound mode is set. (As opposed to + * stop-at-right-column mode.) + */ + private static final int K_WRAPAROUND_MODE_MASK = 1 << 7; + + /** + * Holds multiple DECSET flags. The data is stored this way, rather than in + * separate booleans, to make it easier to implement the save-and-restore + * semantics. The various k*ModeMask masks can be used to extract and modify + * the individual flags current states. + */ + private int mDecFlags; + + /** + * Saves away a snapshot of the DECSET flags. Used to implement save and + * restore escape sequences. + */ + private int mSavedDecFlags; + + // Modes set with Set Mode / Reset Mode + + /** + * True if insert mode (as opposed to replace mode) is active. In insert + * mode new characters are inserted, pushing existing text to the right. + */ + private boolean mInsertMode; + + /** + * Automatic newline mode. Configures whether pressing return on the + * keyboard automatically generates a return as well. Not currently + * implemented. + */ + private boolean mAutomaticNewlineMode; + + /** + * An array of tab stops. mTabStop[i] is true if there is a tab stop set for + * column i. + */ + private boolean[] mTabStop; + + // The margins allow portions of the screen to be locked. + + /** + * The top margin of the screen, for scrolling purposes. Ranges from 0 to + * mRows-2. + */ + private int mTopMargin; + + /** + * The bottom margin of the screen, for scrolling purposes. Ranges from + * mTopMargin + 2 to mRows. (Defines the first row after the scrolling + * region. + */ + private int mBottomMargin; + + /** + * True if the next character to be emitted will be automatically wrapped to + * the next line. Used to disambiguate the case where the cursor is + * positioned on column mColumns-1. + */ + private boolean mAboutToAutoWrap; + + /** + * Used for debugging, counts how many chars have been processed. + */ + private int mProcessedCharCount; + + /** + * Foreground color, 0..7, mask with 8 for bold + */ + private int mForeColor; + + /** + * Background color, 0..7, mask with 8 for underline + */ + private int mBackColor; + + private boolean mInverseColors; + + private boolean mbKeypadApplicationMode; + + private boolean mAlternateCharSet; + + /** + * Used for moving selection up along with the scrolling text + */ + private int mScrollCounter = 0; + + /** + * Construct a terminal emulator that uses the supplied screen + * + * @param screen the screen to render characters into. + * @param columns the number of columns to emulate + * @param rows the number of rows to emulate + * @param termOut the output file descriptor that talks to the pseudo-tty. + */ + public TerminalEmulator(Screen screen, int columns, int rows, + FileOutputStream termOut) { + mScreen = screen; + mRows = rows; + mColumns = columns; + mTabStop = new boolean[mColumns]; + mTermOut = termOut; + reset(); + } + + public void updateSize(int columns, int rows) { + if (mRows == rows && mColumns == columns) { + return; + } + if (columns <= 0) { + throw new IllegalArgumentException("rows:" + columns); + } + + if (rows <= 0) { + throw new IllegalArgumentException("rows:" + rows); + } + + String transcriptText = mScreen.getTranscriptText(); + + mScreen.resize(columns, rows, mForeColor, mBackColor); + + if (mRows != rows) { + mRows = rows; + mTopMargin = 0; + mBottomMargin = mRows; + } + if (mColumns != columns) { + int oldColumns = mColumns; + mColumns = columns; + boolean[] oldTabStop = mTabStop; + mTabStop = new boolean[mColumns]; + int toTransfer = Math.min(oldColumns, columns); + System.arraycopy(oldTabStop, 0, mTabStop, 0, toTransfer); + while (mCursorCol >= columns) { + mCursorCol -= columns; + mCursorRow = Math.min(mBottomMargin-1, mCursorRow + 1); + } + } + mCursorRow = 0; + mCursorCol = 0; + mAboutToAutoWrap = false; + + int end = transcriptText.length()-1; + while ((end >= 0) && transcriptText.charAt(end) == '\n') { + end--; + } + for(int i = 0; i <= end; i++) { + byte c = (byte) transcriptText.charAt(i); + if (c == '\n') { + setCursorCol(0); + doLinefeed(); + } else { + emit(c); + } + } + } + + /** + * Get the cursor's current row. + * + * @return the cursor's current row. + */ + public final int getCursorRow() { + return mCursorRow; + } + + /** + * Get the cursor's current column. + * + * @return the cursor's current column. + */ + public final int getCursorCol() { + return mCursorCol; + } + + public final boolean getKeypadApplicationMode() { + return mbKeypadApplicationMode; + } + + private void setDefaultTabStops() { + for (int i = 0; i < mColumns; i++) { + mTabStop[i] = (i & 7) == 0 && i != 0; + } + } + + /** + * Accept bytes (typically from the pseudo-teletype) and process them. + * + * @param buffer a byte array containing the bytes to be processed + * @param base the first index of the array to process + * @param length the number of bytes in the array to process + */ + public void append(byte[] buffer, int base, int length) { + for (int i = 0; i < length; i++) { + byte b = buffer[base + i]; + try { + if (Term.LOG_CHARACTERS_FLAG) { + char printableB = (char) b; + if (b < 32 || b > 126) { + printableB = ' '; + } + Log.w(Term.LOG_TAG, "'" + Character.toString(printableB) + + "' (" + Integer.toString(b) + ")"); + } + process(b); + mProcessedCharCount++; + } catch (Exception e) { + Log.e(Term.LOG_TAG, "Exception while processing character " + + Integer.toString(mProcessedCharCount) + " code " + + Integer.toString(b), e); + } + } + } + + private void process(byte b) { + switch (b) { + case 0: // NUL + // Do nothing + break; + + case 7: // BEL + // Do nothing + break; + + case 8: // BS + setCursorCol(Math.max(0, mCursorCol - 1)); + break; + + case 9: // HT + // Move to next tab stop, but not past edge of screen + setCursorCol(nextTabStop(mCursorCol)); + break; + + case 13: + setCursorCol(0); + break; + + case 10: // CR + case 11: // VT + case 12: // LF + doLinefeed(); + break; + + case 14: // SO: + setAltCharSet(true); + break; + + case 15: // SI: + setAltCharSet(false); + break; + + + case 24: // CAN + case 26: // SUB + if (mEscapeState != ESC_NONE) { + mEscapeState = ESC_NONE; + emit((byte) 127); + } + break; + + case 27: // ESC + // Always starts an escape sequence + startEscapeSequence(ESC); + break; + + case (byte) 0x9b: // CSI + startEscapeSequence(ESC_LEFT_SQUARE_BRACKET); + break; + + default: + mContinueSequence = false; + switch (mEscapeState) { + case ESC_NONE: + if (b >= 32) { + emit(b); + } + break; + + case ESC: + doEsc(b); + break; + + case ESC_POUND: + doEscPound(b); + break; + + case ESC_SELECT_LEFT_PAREN: + doEscSelectLeftParen(b); + break; + + case ESC_SELECT_RIGHT_PAREN: + doEscSelectRightParen(b); + break; + + case ESC_LEFT_SQUARE_BRACKET: + doEscLeftSquareBracket(b); + break; + + case ESC_LEFT_SQUARE_BRACKET_QUESTION_MARK: + doEscLSBQuest(b); + break; + + default: + unknownSequence(b); + break; + } + if (!mContinueSequence) { + mEscapeState = ESC_NONE; + } + break; + } + } + + private void setAltCharSet(boolean alternateCharSet) { + mAlternateCharSet = alternateCharSet; + } + + private int nextTabStop(int cursorCol) { + for (int i = cursorCol; i < mColumns; i++) { + if (mTabStop[i]) { + return i; + } + } + return mColumns - 1; + } + + private void doEscLSBQuest(byte b) { + int mask = getDecFlagsMask(getArg0(0)); + switch (b) { + case 'h': // Esc [ ? Pn h - DECSET + mDecFlags |= mask; + break; + + case 'l': // Esc [ ? Pn l - DECRST + mDecFlags &= ~mask; + break; + + case 'r': // Esc [ ? Pn r - restore + mDecFlags = (mDecFlags & ~mask) | (mSavedDecFlags & mask); + break; + + case 's': // Esc [ ? Pn s - save + mSavedDecFlags = (mSavedDecFlags & ~mask) | (mDecFlags & mask); + break; + + default: + parseArg(b); + break; + } + + // 132 column mode + if ((mask & K_132_COLUMN_MODE_MASK) != 0) { + // We don't actually set 132 cols, but we do want the + // side effect of clearing the screen and homing the cursor. + blockClear(0, 0, mColumns, mRows); + setCursorRowCol(0, 0); + } + + // origin mode + if ((mask & K_ORIGIN_MODE_MASK) != 0) { + // Home the cursor. + setCursorPosition(0, 0); + } + } + + private int getDecFlagsMask(int argument) { + if (argument >= 1 && argument <= 9) { + return (1 << argument); + } + + return 0; + } + + private void startEscapeSequence(int escapeState) { + mEscapeState = escapeState; + mArgIndex = 0; + for (int j = 0; j < MAX_ESCAPE_PARAMETERS; j++) { + mArgs[j] = -1; + } + } + + private void doLinefeed() { + int newCursorRow = mCursorRow + 1; + if (newCursorRow >= mBottomMargin) { + scroll(); + newCursorRow = mBottomMargin - 1; + } + setCursorRow(newCursorRow); + } + + private void continueSequence() { + mContinueSequence = true; + } + + private void continueSequence(int state) { + mEscapeState = state; + mContinueSequence = true; + } + + private void doEscSelectLeftParen(byte b) { + doSelectCharSet(true, b); + } + + private void doEscSelectRightParen(byte b) { + doSelectCharSet(false, b); + } + + private void doSelectCharSet(boolean isG0CharSet, byte b) { + switch (b) { + case 'A': // United Kingdom character set + break; + case 'B': // ASCII set + break; + case '0': // Special Graphics + break; + case '1': // Alternate character set + break; + case '2': + break; + default: + unknownSequence(b); + } + } + + private void doEscPound(byte b) { + switch (b) { + case '8': // Esc # 8 - DECALN alignment test + mScreen.blockSet(0, 0, mColumns, mRows, 'E', + getForeColor(), getBackColor()); + break; + + default: + unknownSequence(b); + break; + } + } + + private void doEsc(byte b) { + switch (b) { + case '#': + continueSequence(ESC_POUND); + break; + + case '(': + continueSequence(ESC_SELECT_LEFT_PAREN); + break; + + case ')': + continueSequence(ESC_SELECT_RIGHT_PAREN); + break; + + case '7': // DECSC save cursor + mSavedCursorRow = mCursorRow; + mSavedCursorCol = mCursorCol; + break; + + case '8': // DECRC restore cursor + setCursorRowCol(mSavedCursorRow, mSavedCursorCol); + break; + + case 'D': // INDEX + doLinefeed(); + break; + + case 'E': // NEL + setCursorCol(0); + doLinefeed(); + break; + + case 'F': // Cursor to lower-left corner of screen + setCursorRowCol(0, mBottomMargin - 1); + break; + + case 'H': // Tab set + mTabStop[mCursorCol] = true; + break; + + case 'M': // Reverse index + if (mCursorRow <= mTopMargin) { + mScreen.blockCopy(0, mTopMargin, mColumns, mBottomMargin + - (mTopMargin + 1), 0, mTopMargin + 1); + blockClear(0, mTopMargin, mColumns); + } else { + mCursorRow--; + } + + break; + + case 'N': // SS2 + unimplementedSequence(b); + break; + + case '0': // SS3 + unimplementedSequence(b); + break; + + case 'P': // Device control string + unimplementedSequence(b); + break; + + case 'Z': // return terminal ID + sendDeviceAttributes(); + break; + + case '[': + continueSequence(ESC_LEFT_SQUARE_BRACKET); + break; + + case '=': // DECKPAM + mbKeypadApplicationMode = true; + break; + + case '>' : // DECKPNM + mbKeypadApplicationMode = false; + break; + + default: + unknownSequence(b); + break; + } + } + + private void doEscLeftSquareBracket(byte b) { + switch (b) { + case '@': // ESC [ Pn @ - ICH Insert Characters + { + int charsAfterCursor = mColumns - mCursorCol; + int charsToInsert = Math.min(getArg0(1), charsAfterCursor); + int charsToMove = charsAfterCursor - charsToInsert; + mScreen.blockCopy(mCursorCol, mCursorRow, charsToMove, 1, + mCursorCol + charsToInsert, mCursorRow); + blockClear(mCursorCol, mCursorRow, charsToInsert); + } + break; + + case 'A': // ESC [ Pn A - Cursor Up + setCursorRow(Math.max(mTopMargin, mCursorRow - getArg0(1))); + break; + + case 'B': // ESC [ Pn B - Cursor Down + setCursorRow(Math.min(mBottomMargin - 1, mCursorRow + getArg0(1))); + break; + + case 'C': // ESC [ Pn C - Cursor Right + setCursorCol(Math.min(mColumns - 1, mCursorCol + getArg0(1))); + break; + + case 'D': // ESC [ Pn D - Cursor Left + setCursorCol(Math.max(0, mCursorCol - getArg0(1))); + break; + + case 'G': // ESC [ Pn G - Cursor Horizontal Absolute + setCursorCol(Math.min(Math.max(1, getArg0(1)), mColumns) - 1); + break; + + case 'H': // ESC [ Pn ; H - Cursor Position + setHorizontalVerticalPosition(); + break; + + case 'J': // ESC [ Pn J - Erase in Display + switch (getArg0(0)) { + case 0: // Clear below + blockClear(mCursorCol, mCursorRow, mColumns - mCursorCol); + blockClear(0, mCursorRow + 1, mColumns, + mBottomMargin - (mCursorRow + 1)); + break; + + case 1: // Erase from the start of the screen to the cursor. + blockClear(0, mTopMargin, mColumns, mCursorRow - mTopMargin); + blockClear(0, mCursorRow, mCursorCol + 1); + break; + + case 2: // Clear all + blockClear(0, mTopMargin, mColumns, mBottomMargin - mTopMargin); + break; + + default: + unknownSequence(b); + break; + } + break; + + case 'K': // ESC [ Pn K - Erase in Line + switch (getArg0(0)) { + case 0: // Clear to right + blockClear(mCursorCol, mCursorRow, mColumns - mCursorCol); + break; + + case 1: // Erase start of line to cursor (including cursor) + blockClear(0, mCursorRow, mCursorCol + 1); + break; + + case 2: // Clear whole line + blockClear(0, mCursorRow, mColumns); + break; + + default: + unknownSequence(b); + break; + } + break; + + case 'L': // Insert Lines + { + int linesAfterCursor = mBottomMargin - mCursorRow; + int linesToInsert = Math.min(getArg0(1), linesAfterCursor); + int linesToMove = linesAfterCursor - linesToInsert; + mScreen.blockCopy(0, mCursorRow, mColumns, linesToMove, 0, + mCursorRow + linesToInsert); + blockClear(0, mCursorRow, mColumns, linesToInsert); + } + break; + + case 'M': // Delete Lines + { + int linesAfterCursor = mBottomMargin - mCursorRow; + int linesToDelete = Math.min(getArg0(1), linesAfterCursor); + int linesToMove = linesAfterCursor - linesToDelete; + mScreen.blockCopy(0, mCursorRow + linesToDelete, mColumns, + linesToMove, 0, mCursorRow); + blockClear(0, mCursorRow + linesToMove, mColumns, linesToDelete); + } + break; + + case 'P': // Delete Characters + { + int charsAfterCursor = mColumns - mCursorCol; + int charsToDelete = Math.min(getArg0(1), charsAfterCursor); + int charsToMove = charsAfterCursor - charsToDelete; + mScreen.blockCopy(mCursorCol + charsToDelete, mCursorRow, + charsToMove, 1, mCursorCol, mCursorRow); + blockClear(mCursorCol + charsToMove, mCursorRow, charsToDelete); + } + break; + + case 'T': // Mouse tracking + unimplementedSequence(b); + break; + + case '?': // Esc [ ? -- start of a private mode set + continueSequence(ESC_LEFT_SQUARE_BRACKET_QUESTION_MARK); + break; + + case 'c': // Send device attributes + sendDeviceAttributes(); + break; + + case 'd': // ESC [ Pn d - Vert Position Absolute + setCursorRow(Math.min(Math.max(1, getArg0(1)), mRows) - 1); + break; + + case 'f': // Horizontal and Vertical Position + setHorizontalVerticalPosition(); + break; + + case 'g': // Clear tab stop + switch (getArg0(0)) { + case 0: + mTabStop[mCursorCol] = false; + break; + + case 3: + for (int i = 0; i < mColumns; i++) { + mTabStop[i] = false; + } + break; + + default: + // Specified to have no effect. + break; + } + break; + + case 'h': // Set Mode + doSetMode(true); + break; + + case 'l': // Reset Mode + doSetMode(false); + break; + + case 'm': // Esc [ Pn m - character attributes. + selectGraphicRendition(); + break; + + case 'r': // Esc [ Pn ; Pn r - set top and bottom margins + { + // The top margin defaults to 1, the bottom margin + // (unusually for arguments) defaults to mRows. + // + // The escape sequence numbers top 1..23, but we + // number top 0..22. + // The escape sequence numbers bottom 2..24, and + // so do we (because we use a zero based numbering + // scheme, but we store the first line below the + // bottom-most scrolling line. + // As a result, we adjust the top line by -1, but + // we leave the bottom line alone. + // + // Also require that top + 2 <= bottom + + int top = Math.max(0, Math.min(getArg0(1) - 1, mRows - 2)); + int bottom = Math.max(top + 2, Math.min(getArg1(mRows), mRows)); + mTopMargin = top; + mBottomMargin = bottom; + + // The cursor is placed in the home position + setCursorRowCol(mTopMargin, 0); + } + break; + + default: + parseArg(b); + break; + } + } + + private void selectGraphicRendition() { + for (int i = 0; i <= mArgIndex; i++) { + int code = mArgs[i]; + if ( code < 0) { + if (mArgIndex > 0) { + continue; + } else { + code = 0; + } + } + + // See http://en.wikipedia.org/wiki/ANSI_escape_code#graphics + + if (code == 0) { // reset + mInverseColors = false; + mForeColor = 7; + mBackColor = 0; + } else if (code == 1) { // bold + mForeColor |= 0x8; + } else if (code == 4) { // underscore + mBackColor |= 0x8; + } else if (code == 7) { // inverse + mInverseColors = true; + } else if (code == 22) { // Normal color or intensity, neither bright, bold nor faint + mForeColor &= 0x7; + } else if (code == 24) { // underline: none + mBackColor &= 0x7; + } else if (code == 27) { // image: positive + mInverseColors = false; + } else if (code >= 30 && code <= 37) { // foreground color + mForeColor = (mForeColor & 0x8) | (code - 30); + } else if (code == 39) { // set default text color + mForeColor = 7; + mBackColor = mBackColor & 0x7; + } else if (code >= 40 && code <= 47) { // background color + mBackColor = (mBackColor & 0x8) | (code - 40); + } else if (code == 49) { // set default background color + mBackColor = mBackColor & 0x8; // color 0, but preserve underscore. + } else { + if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) { + Log.w(Term.LOG_TAG, String.format("SGR unknown code %d", code)); + } + } + } + } + + private void blockClear(int sx, int sy, int w) { + blockClear(sx, sy, w, 1); + } + + private void blockClear(int sx, int sy, int w, int h) { + mScreen.blockSet(sx, sy, w, h, ' ', getForeColor(), getBackColor()); + } + + private int getForeColor() { + return mInverseColors ? + ((mBackColor & 0x7) | (mForeColor & 0x8)) : mForeColor; + } + + private int getBackColor() { + return mInverseColors ? + ((mForeColor & 0x7) | (mBackColor & 0x8)) : mBackColor; + } + + private void doSetMode(boolean newValue) { + int modeBit = getArg0(0); + switch (modeBit) { + case 4: + mInsertMode = newValue; + break; + + case 20: + mAutomaticNewlineMode = newValue; + break; + + default: + unknownParameter(modeBit); + break; + } + } + + private void setHorizontalVerticalPosition() { + + // Parameters are Row ; Column + + setCursorPosition(getArg1(1) - 1, getArg0(1) - 1); + } + + private void setCursorPosition(int x, int y) { + int effectiveTopMargin = 0; + int effectiveBottomMargin = mRows; + if ((mDecFlags & K_ORIGIN_MODE_MASK) != 0) { + effectiveTopMargin = mTopMargin; + effectiveBottomMargin = mBottomMargin; + } + int newRow = + Math.max(effectiveTopMargin, Math.min(effectiveTopMargin + y, + effectiveBottomMargin - 1)); + int newCol = Math.max(0, Math.min(x, mColumns - 1)); + setCursorRowCol(newRow, newCol); + } + + private void sendDeviceAttributes() { + // This identifies us as a DEC vt100 with advanced + // video options. This is what the xterm terminal + // emulator sends. + byte[] attributes = + { + /* VT100 */ + (byte) 27, (byte) '[', (byte) '?', (byte) '1', + (byte) ';', (byte) '2', (byte) 'c' + + /* VT220 + (byte) 27, (byte) '[', (byte) '?', (byte) '6', + (byte) '0', (byte) ';', + (byte) '1', (byte) ';', + (byte) '2', (byte) ';', + (byte) '6', (byte) ';', + (byte) '8', (byte) ';', + (byte) '9', (byte) ';', + (byte) '1', (byte) '5', (byte) ';', + (byte) 'c' + */ + }; + + write(attributes); + } + + /** + * Send data to the shell process + * @param data + */ + private void write(byte[] data) { + try { + mTermOut.write(data); + mTermOut.flush(); + } catch (IOException e) { + // Ignore exception + // We don't really care if the receiver isn't listening. + // We just make a best effort to answer the query. + } + } + + private void scroll() { + //System.out.println("Scroll(): mTopMargin " + mTopMargin + " mBottomMargin " + mBottomMargin); + mScrollCounter ++; + mScreen.scroll(mTopMargin, mBottomMargin, + getForeColor(), getBackColor()); + } + + /** + * Process the next ASCII character of a parameter. + * + * @param b The next ASCII character of the paramater sequence. + */ + private void parseArg(byte b) { + if (b >= '0' && b <= '9') { + if (mArgIndex < mArgs.length) { + int oldValue = mArgs[mArgIndex]; + int thisDigit = b - '0'; + int value; + if (oldValue >= 0) { + value = oldValue * 10 + thisDigit; + } else { + value = thisDigit; + } + mArgs[mArgIndex] = value; + } + continueSequence(); + } else if (b == ';') { + if (mArgIndex < mArgs.length) { + mArgIndex++; + } + continueSequence(); + } else { + unknownSequence(b); + } + } + + private int getArg0(int defaultValue) { + return getArg(0, defaultValue); + } + + private int getArg1(int defaultValue) { + return getArg(1, defaultValue); + } + + private int getArg(int index, int defaultValue) { + int result = mArgs[index]; + if (result < 0) { + result = defaultValue; + } + return result; + } + + private void unimplementedSequence(byte b) { + if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) { + logError("unimplemented", b); + } + finishSequence(); + } + + private void unknownSequence(byte b) { + if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) { + logError("unknown", b); + } + finishSequence(); + } + + private void unknownParameter(int parameter) { + if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) { + StringBuilder buf = new StringBuilder(); + buf.append("Unknown parameter"); + buf.append(parameter); + logError(buf.toString()); + } + } + + private void logError(String errorType, byte b) { + if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) { + StringBuilder buf = new StringBuilder(); + buf.append(errorType); + buf.append(" sequence "); + buf.append(" EscapeState: "); + buf.append(mEscapeState); + buf.append(" char: '"); + buf.append((char) b); + buf.append("' ("); + buf.append(b); + buf.append(")"); + boolean firstArg = true; + for (int i = 0; i <= mArgIndex; i++) { + int value = mArgs[i]; + if (value >= 0) { + if (firstArg) { + firstArg = false; + buf.append("args = "); + } + buf.append(String.format("%d; ", value)); + } + } + logError(buf.toString()); + } + } + + private void logError(String error) { + if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) { + Log.e(Term.LOG_TAG, error); + } + finishSequence(); + } + + private void finishSequence() { + mEscapeState = ESC_NONE; + } + + private boolean autoWrapEnabled() { + // Always enable auto wrap, because it's useful on a small screen + return true; + // return (mDecFlags & K_WRAPAROUND_MODE_MASK) != 0; + } + + /** + * Send an ASCII character to the screen. + * + * @param b the ASCII character to display. + */ + private void emit(byte b) { + boolean autoWrap = autoWrapEnabled(); + + if (autoWrap) { + if (mCursorCol == mColumns - 1 && mAboutToAutoWrap) { + mScreen.setLineWrap(mCursorRow); + mCursorCol = 0; + if (mCursorRow + 1 < mBottomMargin) { + mCursorRow++; + } else { + scroll(); + } + } + } + + if (mInsertMode) { // Move character to right one space + int destCol = mCursorCol + 1; + if (destCol < mColumns) { + mScreen.blockCopy(mCursorCol, mCursorRow, mColumns - destCol, + 1, destCol, mCursorRow); + } + } + + mScreen.set(mCursorCol, mCursorRow, b, getForeColor(), getBackColor()); + + if (autoWrap) { + mAboutToAutoWrap = (mCursorCol == mColumns - 1); + } + + mCursorCol = Math.min(mCursorCol + 1, mColumns - 1); + } + + private void setCursorRow(int row) { + mCursorRow = row; + mAboutToAutoWrap = false; + } + + private void setCursorCol(int col) { + mCursorCol = col; + mAboutToAutoWrap = false; + } + + private void setCursorRowCol(int row, int col) { + mCursorRow = Math.min(row, mRows-1); + mCursorCol = Math.min(col, mColumns-1); + mAboutToAutoWrap = false; + } + + public int getScrollCounter() { + return mScrollCounter; + } + + public void clearScrollCounter() { + mScrollCounter = 0; + } + + /** + * Reset the terminal emulator to its initial state. + */ + public void reset() { + mCursorRow = 0; + mCursorCol = 0; + mArgIndex = 0; + mContinueSequence = false; + mEscapeState = ESC_NONE; + mSavedCursorRow = 0; + mSavedCursorCol = 0; + mDecFlags = 0; + mSavedDecFlags = 0; + mInsertMode = false; + mAutomaticNewlineMode = false; + mTopMargin = 0; + mBottomMargin = mRows; + mAboutToAutoWrap = false; + mForeColor = 7; + mBackColor = 0; + mInverseColors = false; + mbKeypadApplicationMode = false; + mAlternateCharSet = false; + // mProcessedCharCount is preserved unchanged. + setDefaultTabStops(); + blockClear(0, 0, mColumns, mRows); + } + + public String getTranscriptText() { + return mScreen.getTranscriptText(); + } + public String getSelectedText(int x1, int y1, int x2, int y2) { + return mScreen.getSelectedText(x1, y1, x2, y2); + } +} diff --git a/src/jackpal/androidterm/session/TranscriptScreen.java b/src/jackpal/androidterm/session/TranscriptScreen.java new file mode 100644 index 000000000..c79e4030f --- /dev/null +++ b/src/jackpal/androidterm/session/TranscriptScreen.java @@ -0,0 +1,464 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jackpal.androidterm.session; + +import android.graphics.Canvas; +import android.util.Log; + +import jackpal.androidterm.model.Screen; +import jackpal.androidterm.model.TextRenderer; + +/** + * A TranscriptScreen is a screen that remembers data that's been scrolled. The + * old data is stored in a ring buffer to minimize the amount of copying that + * needs to be done. The transcript does its own drawing, to avoid having to + * expose its internal data structures. + */ +public class TranscriptScreen implements Screen { + private static final String TAG = "TranscriptScreen"; + + /** + * The width of the transcript, in characters. Fixed at initialization. + */ + private int mColumns; + + /** + * The total number of rows in the transcript and the screen. Fixed at + * initialization. + */ + private int mTotalRows; + + /** + * The number of rows in the active portion of the transcript. Doesn't + * include the screen. + */ + private int mActiveTranscriptRows; + + /** + * Which row is currently the topmost line of the transcript. Used to + * implement a circular buffer. + */ + private int mHead; + + /** + * The number of active rows, includes both the transcript and the screen. + */ + private int mActiveRows; + + /** + * The number of rows in the screen. + */ + private int mScreenRows; + + /** + * The data for both the screen and the transcript. The first mScreenRows * + * mLineWidth characters are the screen, the rest are the transcript. + * The low byte encodes the ASCII character, the high byte encodes the + * foreground and background colors, plus underline and bold. + */ + private char[] mData; + + /** + * The data's stored as color-encoded chars, but the drawing routines require chars, so we + * need a temporary buffer to hold a row's worth of characters. + */ + private char[] mRowBuffer; + + /** + * Flags that keep track of whether the current line logically wraps to the + * next line. This is used when resizing the screen and when copying to the + * clipboard or an email attachment + */ + + private boolean[] mLineWrap; + + /** + * Create a transcript screen. + * + * @param columns the width of the screen in characters. + * @param totalRows the height of the entire text area, in rows of text. + * @param screenRows the height of just the screen, not including the + * transcript that holds lines that have scrolled off the top of the + * screen. + */ + public TranscriptScreen(int columns, int totalRows, int screenRows, + int foreColor, int backColor) { + init(columns, totalRows, screenRows, foreColor, backColor); + } + + private void init(int columns, int totalRows, int screenRows, int foreColor, int backColor) { + mColumns = columns; + mTotalRows = totalRows; + mActiveTranscriptRows = 0; + mHead = 0; + mActiveRows = screenRows; + mScreenRows = screenRows; + int totalSize = columns * totalRows; + mData = new char[totalSize]; + blockSet(0, 0, mColumns, mScreenRows, ' ', foreColor, backColor); + mRowBuffer = new char[columns]; + mLineWrap = new boolean[totalRows]; + consistencyCheck(); + } + + /** + * Convert a row value from the public external coordinate system to our + * internal private coordinate system. External coordinate system: + * -mActiveTranscriptRows to mScreenRows-1, with the screen being + * 0..mScreenRows-1 Internal coordinate system: 0..mScreenRows-1 rows of + * mData are the visible rows. mScreenRows..mActiveRows - 1 are the + * transcript, stored as a circular buffer. + * + * @param row a row in the external coordinate system. + * @return The row corresponding to the input argument in the private + * coordinate system. + */ + private int externalToInternalRow(int row) { + if (row < -mActiveTranscriptRows || row >= mScreenRows) { + String errorMessage = "externalToInternalRow "+ row + + " " + mActiveTranscriptRows + " " + mScreenRows; + Log.e(TAG, errorMessage); + throw new IllegalArgumentException(errorMessage); + } + if (row >= 0) { + return row; // This is a visible row. + } + return mScreenRows + + ((mHead + mActiveTranscriptRows + row) % mActiveTranscriptRows); + } + + private int getOffset(int externalLine) { + return externalToInternalRow(externalLine) * mColumns; + } + + private int getOffset(int x, int y) { + return getOffset(y) + x; + } + + public void setLineWrap(int row) { + mLineWrap[externalToInternalRow(row)] = true; + } + + /** + * Store byte b into the screen at location (x, y) + * + * @param x X coordinate (also known as column) + * @param y Y coordinate (also known as row) + * @param b ASCII character to store + * @param foreColor the foreground color + * @param backColor the background color + */ + public void set(int x, int y, byte b, int foreColor, int backColor) { + mData[getOffset(x, y)] = encode(b, foreColor, backColor); + } + + private char encode(int b, int foreColor, int backColor) { + return (char) ((foreColor << 12) | (backColor << 8) | b); + } + + /** + * Scroll the screen down one line. To scroll the whole screen of a 24 line + * screen, the arguments would be (0, 24). + * + * @param topMargin First line that is scrolled. + * @param bottomMargin One line after the last line that is scrolled. + */ + public void scroll(int topMargin, int bottomMargin, int foreColor, + int backColor) { + // Separate out reasons so that stack crawls help us + // figure out which condition was violated. + if (topMargin > bottomMargin - 1) { + throw new IllegalArgumentException(); + } + + if (topMargin > mScreenRows - 1) { + throw new IllegalArgumentException(); + } + + if (bottomMargin > mScreenRows) { + throw new IllegalArgumentException(); + } + + // Adjust the transcript so that the last line of the transcript + // is ready to receive the newly scrolled data + consistencyCheck(); + int expansionRows = Math.min(1, mTotalRows - mActiveRows); + int rollRows = 1 - expansionRows; + mActiveRows += expansionRows; + mActiveTranscriptRows += expansionRows; + if (mActiveTranscriptRows > 0) { + mHead = (mHead + rollRows) % mActiveTranscriptRows; + } + consistencyCheck(); + + // Block move the scroll line to the transcript + int topOffset = getOffset(topMargin); + int destOffset = getOffset(-1); + System.arraycopy(mData, topOffset, mData, destOffset, mColumns); + + int topLine = externalToInternalRow(topMargin); + int destLine = externalToInternalRow(-1); + System.arraycopy(mLineWrap, topLine, mLineWrap, destLine, 1); + + // Block move the scrolled data up + int numScrollChars = (bottomMargin - topMargin - 1) * mColumns; + System.arraycopy(mData, topOffset + mColumns, mData, topOffset, + numScrollChars); + int numScrollLines = (bottomMargin - topMargin - 1); + System.arraycopy(mLineWrap, topLine + 1, mLineWrap, topLine, + numScrollLines); + + // Erase the bottom line of the scroll region + blockSet(0, bottomMargin - 1, mColumns, 1, ' ', foreColor, backColor); + mLineWrap[externalToInternalRow(bottomMargin-1)] = false; + } + + private void consistencyCheck() { + checkPositive(mColumns); + checkPositive(mTotalRows); + checkRange(0, mActiveTranscriptRows, mTotalRows); + if (mActiveTranscriptRows == 0) { + checkEqual(mHead, 0); + } else { + checkRange(0, mHead, mActiveTranscriptRows-1); + } + checkEqual(mScreenRows + mActiveTranscriptRows, mActiveRows); + checkRange(0, mScreenRows, mTotalRows); + + checkEqual(mTotalRows, mLineWrap.length); + checkEqual(mTotalRows*mColumns, mData.length); + checkEqual(mColumns, mRowBuffer.length); + } + + private void checkPositive(int n) { + if (n < 0) { + throw new IllegalArgumentException("checkPositive " + n); + } + } + + private void checkRange(int a, int b, int c) { + if (a > b || b > c) { + throw new IllegalArgumentException("checkRange " + a + " <= " + b + " <= " + c); + } + } + + private void checkEqual(int a, int b) { + if (a != b) { + throw new IllegalArgumentException("checkEqual " + a + " == " + b); + } + } + + /** + * Block copy characters from one position in the screen to another. The two + * positions can overlap. All characters of the source and destination must + * be within the bounds of the screen, or else an InvalidParemeterException + * will be thrown. + * + * @param sx source X coordinate + * @param sy source Y coordinate + * @param w width + * @param h height + * @param dx destination X coordinate + * @param dy destination Y coordinate + */ + public void blockCopy(int sx, int sy, int w, int h, int dx, int dy) { + if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows + || dx < 0 || dx + w > mColumns || dy < 0 + || dy + h > mScreenRows) { + throw new IllegalArgumentException(); + } + if (sy > dy) { + // Move in increasing order + for (int y = 0; y < h; y++) { + int srcOffset = getOffset(sx, sy + y); + int dstOffset = getOffset(dx, dy + y); + System.arraycopy(mData, srcOffset, mData, dstOffset, w); + } + } else { + // Move in decreasing order + for (int y = 0; y < h; y++) { + int y2 = h - (y + 1); + int srcOffset = getOffset(sx, sy + y2); + int dstOffset = getOffset(dx, dy + y2); + System.arraycopy(mData, srcOffset, mData, dstOffset, w); + } + } + } + + /** + * Block set characters. All characters must be within the bounds of the + * screen, or else and InvalidParemeterException will be thrown. Typically + * this is called with a "val" argument of 32 to clear a block of + * characters. + * + * @param sx source X + * @param sy source Y + * @param w width + * @param h height + * @param val value to set. + */ + public void blockSet(int sx, int sy, int w, int h, int val, + int foreColor, int backColor) { + if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows) { + throw new IllegalArgumentException(); + } + char[] data = mData; + char encodedVal = encode(val, foreColor, backColor); + for (int y = 0; y < h; y++) { + int offset = getOffset(sx, sy + y); + for (int x = 0; x < w; x++) { + data[offset + x] = encodedVal; + } + } + } + + /** + * Draw a row of text. Out-of-bounds rows are blank, not errors. + * + * @param row The row of text to draw. + * @param canvas The canvas to draw to. + * @param x The x coordinate origin of the drawing + * @param y The y coordinate origin of the drawing + * @param renderer The renderer to use to draw the text + * @param cx the cursor X coordinate, -1 means don't draw it + * @param selx1 the text selection start X coordinate + * @param selx2 the text selection end X coordinate, if equals to selx1 don't draw selection + * @param imeText current IME text, to be rendered at cursor + */ + public final void drawText(int row, Canvas canvas, float x, float y, + TextRenderer renderer, int cx, int selx1, int selx2, String imeText) { + + // Out-of-bounds rows are blank. + if (row < -mActiveTranscriptRows || row >= mScreenRows) { + return; + } + + // Copy the data from the byte array to a char array so they can + // be drawn. + + int offset = getOffset(row); + char[] rowBuffer = mRowBuffer; + char[] data = mData; + int columns = mColumns; + int lastColors = 0; + int lastRunStart = -1; + final int CURSOR_MASK = 0x10000; + for (int i = 0; i < columns; i++) { + char c = data[offset + i]; + int colors = (char) (c & 0xff00); + if (cx == i || (i >= selx1 && i <= selx2)) { + // Set cursor background color: + colors |= CURSOR_MASK; + } + rowBuffer[i] = (char) (c & 0x00ff); + if (colors != lastColors) { + if (lastRunStart >= 0) { + renderer.drawTextRun(canvas, x, y, lastRunStart, rowBuffer, + lastRunStart, i - lastRunStart, + (lastColors & CURSOR_MASK) != 0, + 0xf & (lastColors >> 12), 0xf & (lastColors >> 8)); + } + lastColors = colors; + lastRunStart = i; + } + } + if (lastRunStart >= 0) { + renderer.drawTextRun(canvas, x, y, lastRunStart, rowBuffer, + lastRunStart, columns - lastRunStart, + (lastColors & CURSOR_MASK) != 0, + 0xf & (lastColors >> 12), 0xf & (lastColors >> 8)); + } + + if (cx >= 0 && imeText.length() > 0) { + int imeLength = Math.min(columns, imeText.length()); + int imeOffset = imeText.length() - imeLength; + int imePosition = Math.min(cx, columns - imeLength); + renderer.drawTextRun(canvas, x, y, imePosition, imeText.toCharArray(), + imeOffset, imeLength, true, 0x0f, 0x00); + } + } + + /** + * Get the count of active rows. + * + * @return the count of active rows. + */ + public int getActiveRows() { + return mActiveRows; + } + + /** + * Get the count of active transcript rows. + * + * @return the count of active transcript rows. + */ + public int getActiveTranscriptRows() { + return mActiveTranscriptRows; + } + + public String getTranscriptText() { + return internalGetTranscriptText(true, 0, -mActiveTranscriptRows, mColumns, mScreenRows); + } + + public String getSelectedText(int selX1, int selY1, int selX2, int selY2) { + return internalGetTranscriptText(true, selX1, selY1, selX2, selY2); + } + + private String internalGetTranscriptText(boolean stripColors, int selX1, int selY1, int selX2, int selY2) { + StringBuilder builder = new StringBuilder(); + char[] rowBuffer = mRowBuffer; + char[] data = mData; + int columns = mColumns; + for (int row = -mActiveTranscriptRows; row < mScreenRows; row++) { + int offset = getOffset(row); + int lastPrintingChar = -1; + for (int column = 0; column < columns; column++) { + char c = data[offset + column]; + if (stripColors) { + c = (char) (c & 0xff); + } + if ((c & 0xff) != ' ') { + lastPrintingChar = column; + } + rowBuffer[column] = c; + } + if ( row >= selY1 && row <= selY2 ) { + int x1 = 0; + int x2 = 0; + if ( row == selY1 ) { + x1 = selX1; + } + if ( row == selY2 ) { + x2 = selX2; + } else { + x2 = columns; + } + if (mLineWrap[externalToInternalRow(row)]) { + builder.append(rowBuffer, x1, x2 - x1); + } else { + builder.append(rowBuffer, x1, Math.max(0, Math.min(x2 - x1 + 1, lastPrintingChar + 1 - x1))); + builder.append('\n'); + } + } + } + return builder.toString(); + } + + public void resize(int columns, int rows, int foreColor, int backColor) { + init(columns, mTotalRows, rows, foreColor, backColor); + } +} diff --git a/src/jackpal/androidterm/util/ByteQueue.java b/src/jackpal/androidterm/util/ByteQueue.java new file mode 100644 index 000000000..debee079b --- /dev/null +++ b/src/jackpal/androidterm/util/ByteQueue.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jackpal.androidterm.util; + +/** + * A multi-thread-safe produce-consumer byte array. + * Only allows one producer and one consumer. + */ + +public class ByteQueue { + public ByteQueue(int size) { + mBuffer = new byte[size]; + } + + public int getBytesAvailable() { + synchronized(this) { + return mStoredBytes; + } + } + + public int read(byte[] buffer, int offset, int length) + throws InterruptedException { + if (length + offset > buffer.length) { + throw + new IllegalArgumentException("length + offset > buffer.length"); + } + if (length < 0) { + throw + new IllegalArgumentException("length < 0"); + + } + if (length == 0) { + return 0; + } + synchronized(this) { + while (mStoredBytes == 0) { + wait(); + } + int totalRead = 0; + int bufferLength = mBuffer.length; + boolean wasFull = bufferLength == mStoredBytes; + while (length > 0 && mStoredBytes > 0) { + int oneRun = Math.min(bufferLength - mHead, mStoredBytes); + int bytesToCopy = Math.min(length, oneRun); + System.arraycopy(mBuffer, mHead, buffer, offset, bytesToCopy); + mHead += bytesToCopy; + if (mHead >= bufferLength) { + mHead = 0; + } + mStoredBytes -= bytesToCopy; + length -= bytesToCopy; + offset += bytesToCopy; + totalRead += bytesToCopy; + } + if (wasFull) { + notify(); + } + return totalRead; + } + } + + public void write(byte[] buffer, int offset, int length) + throws InterruptedException { + if (length + offset > buffer.length) { + throw + new IllegalArgumentException("length + offset > buffer.length"); + } + if (length < 0) { + throw + new IllegalArgumentException("length < 0"); + + } + if (length == 0) { + return; + } + synchronized(this) { + int bufferLength = mBuffer.length; + boolean wasEmpty = mStoredBytes == 0; + while (length > 0) { + while(bufferLength == mStoredBytes) { + wait(); + } + int tail = mHead + mStoredBytes; + int oneRun; + if (tail >= bufferLength) { + tail = tail - bufferLength; + oneRun = mHead - tail; + } else { + oneRun = bufferLength - tail; + } + int bytesToCopy = Math.min(oneRun, length); + System.arraycopy(buffer, offset, mBuffer, tail, bytesToCopy); + offset += bytesToCopy; + mStoredBytes += bytesToCopy; + length -= bytesToCopy; + } + if (wasEmpty) { + notify(); + } + } + } + + private byte[] mBuffer; + private int mHead; + private int mStoredBytes; +} diff --git a/src/jackpal/androidterm/ServiceForegroundCompat.java b/src/jackpal/androidterm/util/ServiceForegroundCompat.java similarity index 99% rename from src/jackpal/androidterm/ServiceForegroundCompat.java rename to src/jackpal/androidterm/util/ServiceForegroundCompat.java index 984bcddeb..93fb979e4 100644 --- a/src/jackpal/androidterm/ServiceForegroundCompat.java +++ b/src/jackpal/androidterm/util/ServiceForegroundCompat.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package jackpal.androidterm; +package jackpal.androidterm.util; import java.lang.reflect.Method; import java.lang.reflect.InvocationTargetException; From cbd439b630dbf08636b84c9be0c5a81dcfde726b Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Sat, 27 Aug 2011 12:12:54 -0700 Subject: [PATCH 081/847] This patch isn't really necessary, but it makes life easier for me. For local builds, I change the package name to net.steven676.android.terminal, which allows co-installation with the Market version; however, if the debug constants stay in the Term class, I have to change every file using them to either belong to the new package, or else import net.steven676.android.terminal.Term. --- src/jackpal/androidterm/EmulatorView.java | 44 ++++++++-------- src/jackpal/androidterm/Term.java | 47 ++++------------- src/jackpal/androidterm/TermDebug.java | 52 +++++++++++++++++++ src/jackpal/androidterm/TermService.java | 2 +- .../androidterm/session/TerminalEmulator.java | 24 ++++----- 5 files changed, 96 insertions(+), 73 deletions(-) create mode 100644 src/jackpal/androidterm/TermDebug.java diff --git a/src/jackpal/androidterm/EmulatorView.java b/src/jackpal/androidterm/EmulatorView.java index 88a2249be..008063b1c 100644 --- a/src/jackpal/androidterm/EmulatorView.java +++ b/src/jackpal/androidterm/EmulatorView.java @@ -64,7 +64,7 @@ public class EmulatorView extends View implements GestureDetector.OnGestureListener { private final String TAG = "EmulatorView"; - private final boolean LOG_KEY_EVENTS = Term.DEBUG && false; + private final boolean LOG_KEY_EVENTS = TermDebug.DEBUG && false; private Term mTerm; @@ -347,7 +347,7 @@ private void mapAndSend(int c) throws IOException { } public boolean beginBatchEdit() { - if (Term.LOG_IME) { + if (TermDebug.LOG_IME) { Log.w(TAG, "beginBatchEdit"); } setImeBuffer(""); @@ -359,21 +359,21 @@ public boolean beginBatchEdit() { } public boolean clearMetaKeyStates(int arg0) { - if (Term.LOG_IME) { + if (TermDebug.LOG_IME) { Log.w(TAG, "clearMetaKeyStates " + arg0); } return false; } public boolean commitCompletion(CompletionInfo arg0) { - if (Term.LOG_IME) { + if (TermDebug.LOG_IME) { Log.w(TAG, "commitCompletion " + arg0); } return false; } public boolean endBatchEdit() { - if (Term.LOG_IME) { + if (TermDebug.LOG_IME) { Log.w(TAG, "endBatchEdit"); } mInBatchEdit = false; @@ -381,7 +381,7 @@ public boolean endBatchEdit() { } public boolean finishComposingText() { - if (Term.LOG_IME) { + if (TermDebug.LOG_IME) { Log.w(TAG, "finishComposingText"); } sendText(mImeBuffer); @@ -393,7 +393,7 @@ public boolean finishComposingText() { } public int getCursorCapsMode(int arg0) { - if (Term.LOG_IME) { + if (TermDebug.LOG_IME) { Log.w(TAG, "getCursorCapsMode(" + arg0 + ")"); } return 0; @@ -401,14 +401,14 @@ public int getCursorCapsMode(int arg0) { public ExtractedText getExtractedText(ExtractedTextRequest arg0, int arg1) { - if (Term.LOG_IME) { + if (TermDebug.LOG_IME) { Log.w(TAG, "getExtractedText" + arg0 + "," + arg1); } return null; } public CharSequence getTextAfterCursor(int n, int flags) { - if (Term.LOG_IME) { + if (TermDebug.LOG_IME) { Log.w(TAG, "getTextAfterCursor(" + n + "," + flags + ")"); } int len = Math.min(n, mImeBuffer.length() - mCursor); @@ -419,7 +419,7 @@ public CharSequence getTextAfterCursor(int n, int flags) { } public CharSequence getTextBeforeCursor(int n, int flags) { - if (Term.LOG_IME) { + if (TermDebug.LOG_IME) { Log.w(TAG, "getTextBeforeCursor(" + n + "," + flags + ")"); } int len = Math.min(n, mCursor); @@ -430,35 +430,35 @@ public CharSequence getTextBeforeCursor(int n, int flags) { } public boolean performContextMenuAction(int arg0) { - if (Term.LOG_IME) { + if (TermDebug.LOG_IME) { Log.w(TAG, "performContextMenuAction" + arg0); } return true; } public boolean performPrivateCommand(String arg0, Bundle arg1) { - if (Term.LOG_IME) { + if (TermDebug.LOG_IME) { Log.w(TAG, "performPrivateCommand" + arg0 + "," + arg1); } return true; } public boolean reportFullscreenMode(boolean arg0) { - if (Term.LOG_IME) { + if (TermDebug.LOG_IME) { Log.w(TAG, "reportFullscreenMode" + arg0); } return true; } public boolean commitCorrection (CorrectionInfo correctionInfo) { - if (Term.LOG_IME) { + if (TermDebug.LOG_IME) { Log.w(TAG, "commitCorrection"); } return true; } public boolean commitText(CharSequence text, int newCursorPosition) { - if (Term.LOG_IME) { + if (TermDebug.LOG_IME) { Log.w(TAG, "commitText(\"" + text + "\", " + newCursorPosition + ")"); } clearComposingText(); @@ -482,7 +482,7 @@ private void clearComposingText() { } public boolean deleteSurroundingText(int leftLength, int rightLength) { - if (Term.LOG_IME) { + if (TermDebug.LOG_IME) { Log.w(TAG, "deleteSurroundingText(" + leftLength + "," + rightLength + ")"); } @@ -501,7 +501,7 @@ public boolean deleteSurroundingText(int leftLength, int rightLength) { } public boolean performEditorAction(int actionCode) { - if (Term.LOG_IME) { + if (TermDebug.LOG_IME) { Log.w(TAG, "performEditorAction(" + actionCode + ")"); } if (actionCode == EditorInfo.IME_ACTION_UNSPECIFIED) { @@ -512,7 +512,7 @@ public boolean performEditorAction(int actionCode) { } public boolean sendKeyEvent(KeyEvent event) { - if (Term.LOG_IME) { + if (TermDebug.LOG_IME) { Log.w(TAG, "sendKeyEvent(" + event + ")"); } // Some keys are sent here rather than to commitText. @@ -524,7 +524,7 @@ public boolean sendKeyEvent(KeyEvent event) { } public boolean setComposingText(CharSequence text, int newCursorPosition) { - if (Term.LOG_IME) { + if (TermDebug.LOG_IME) { Log.w(TAG, "setComposingText(\"" + text + "\", " + newCursorPosition + ")"); } setImeBuffer(mImeBuffer.substring(0, mComposingTextStart) + @@ -536,7 +536,7 @@ public boolean setComposingText(CharSequence text, int newCursorPosition) { } public boolean setSelection(int start, int end) { - if (Term.LOG_IME) { + if (TermDebug.LOG_IME) { Log.w(TAG, "setSelection" + start + "," + end); } int length = mImeBuffer.length(); @@ -552,7 +552,7 @@ public boolean setSelection(int start, int end) { } public boolean setComposingRegion(int start, int end) { - if (Term.LOG_IME) { + if (TermDebug.LOG_IME) { Log.w(TAG, "setComposingRegion " + start + "," + end); } if (start < end && start > 0 && end < mImeBuffer.length()) { @@ -564,7 +564,7 @@ public boolean setComposingRegion(int start, int end) { } public CharSequence getSelectedText(int flags) { - if (Term.LOG_IME) { + if (TermDebug.LOG_IME) { Log.w(TAG, "getSelectedText " + flags); } return mImeBuffer.substring(mSelectedTextStart, mSelectedTextEnd+1); diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index 584eaf782..99237052e 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -55,35 +55,6 @@ */ public class Term extends Activity { - /** - * Set to true to add debugging code and logging. - */ - public static final boolean DEBUG = false; - - /** - * Set to true to log IME calls. - */ - public static final boolean LOG_IME = DEBUG && false; - - /** - * Set to true to log each character received from the remote process to the - * android log, which makes it easier to debug some kinds of problems with - * emulating escape sequences and control codes. - */ - public static final boolean LOG_CHARACTERS_FLAG = DEBUG && false; - - /** - * Set to true to log unknown escape sequences. - */ - public static final boolean LOG_UNKNOWN_ESCAPE_SEQUENCES = DEBUG && false; - - /** - * The tag we use when logging, so that our messages can be distinguished - * from other messages in the log. Public because it's used by several - * classes. - */ - public static final String LOG_TAG = "Term"; - /** * Our main view. Displays the emulated terminal screen. */ @@ -201,7 +172,7 @@ public class Term extends Activity { @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); - Log.e(Term.LOG_TAG, "onCreate"); + Log.e(TermDebug.LOG_TAG, "onCreate"); mPrefs = PreferenceManager.getDefaultSharedPreferences(this); readPrefs(); @@ -228,9 +199,9 @@ public void onCreate(Bundle icicle) { registerForContextMenu(mEmulatorView); PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE); - mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, Term.LOG_TAG); + mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TermDebug.LOG_TAG); WifiManager wm = (WifiManager)getSystemService(Context.WIFI_SERVICE); - mWifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL, Term.LOG_TAG); + mWifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL, TermDebug.LOG_TAG); updatePrefs(); mAlreadyStarted = true; @@ -271,9 +242,9 @@ public void handleMessage(Message msg) { Runnable watchForDeath = new Runnable() { public void run() { - Log.i(Term.LOG_TAG, "waiting for: " + mProcId); + Log.i(TermDebug.LOG_TAG, "waiting for: " + mProcId); int result = Exec.waitFor(mProcId); - Log.i(Term.LOG_TAG, "Subprocess exited: " + result); + Log.i(TermDebug.LOG_TAG, "Subprocess exited: " + result); handler.sendEmptyMessage(result); } @@ -407,7 +378,7 @@ private void readPrefs() { String newShell = readStringPref(SHELL_KEY, mShell); if ((newShell == null) || ! newShell.equals(mShell)) { if (mShell != null) { - Log.i(Term.LOG_TAG, "New shell set. Restarting."); + Log.i(TermDebug.LOG_TAG, "New shell set. Restarting."); restart(); } mShell = newShell; @@ -419,7 +390,7 @@ private void readPrefs() { if ((newInitialCommand == null) || ! newInitialCommand.equals(mInitialCommand)) { if (mInitialCommand != null) { - Log.i(Term.LOG_TAG, "New initial command set. Restarting."); + Log.i(TermDebug.LOG_TAG, "New initial command set. Restarting."); restart(); } mInitialCommand = newInitialCommand; @@ -620,13 +591,13 @@ private void doPaste() { try { utf8 = paste.toString().getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { - Log.e(Term.LOG_TAG, "UTF-8 encoding not found."); + Log.e(TermDebug.LOG_TAG, "UTF-8 encoding not found."); return; } try { mTermOut.write(utf8); } catch (IOException e) { - Log.e(Term.LOG_TAG, "could not write paste text to terminal."); + Log.e(TermDebug.LOG_TAG, "could not write paste text to terminal."); } } diff --git a/src/jackpal/androidterm/TermDebug.java b/src/jackpal/androidterm/TermDebug.java new file mode 100644 index 000000000..bb110c0c4 --- /dev/null +++ b/src/jackpal/androidterm/TermDebug.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jackpal.androidterm; + +/** + * Debug settings. + */ + +public class TermDebug { + /** + * Set to true to add debugging code and logging. + */ + public static final boolean DEBUG = false; + + /** + * Set to true to log IME calls. + */ + public static final boolean LOG_IME = DEBUG && false; + + /** + * Set to true to log each character received from the remote process to the + * android log, which makes it easier to debug some kinds of problems with + * emulating escape sequences and control codes. + */ + public static final boolean LOG_CHARACTERS_FLAG = DEBUG && false; + + /** + * Set to true to log unknown escape sequences. + */ + public static final boolean LOG_UNKNOWN_ESCAPE_SEQUENCES = DEBUG && false; + + /** + * The tag we use when logging, so that our messages can be distinguished + * from other messages in the log. Public because it's used by several + * classes. + */ + public static final String LOG_TAG = "Term"; +} diff --git a/src/jackpal/androidterm/TermService.java b/src/jackpal/androidterm/TermService.java index 060ffad16..fbf28117e 100644 --- a/src/jackpal/androidterm/TermService.java +++ b/src/jackpal/androidterm/TermService.java @@ -61,7 +61,7 @@ public void onCreate() { notification.setLatestEventInfo(this, getText(R.string.application_terminal), getText(R.string.service_notify_text), pendingIntent); compat.startForeground(RUNNING_NOTIFICATION, notification); - Log.d(Term.LOG_TAG, "TermService started"); + Log.d(TermDebug.LOG_TAG, "TermService started"); return; } diff --git a/src/jackpal/androidterm/session/TerminalEmulator.java b/src/jackpal/androidterm/session/TerminalEmulator.java index 7b5e559e9..6ad27f525 100644 --- a/src/jackpal/androidterm/session/TerminalEmulator.java +++ b/src/jackpal/androidterm/session/TerminalEmulator.java @@ -21,7 +21,7 @@ import android.util.Log; -import jackpal.androidterm.Term; +import jackpal.androidterm.TermDebug; import jackpal.androidterm.model.Screen; /** @@ -355,18 +355,18 @@ public void append(byte[] buffer, int base, int length) { for (int i = 0; i < length; i++) { byte b = buffer[base + i]; try { - if (Term.LOG_CHARACTERS_FLAG) { + if (TermDebug.LOG_CHARACTERS_FLAG) { char printableB = (char) b; if (b < 32 || b > 126) { printableB = ' '; } - Log.w(Term.LOG_TAG, "'" + Character.toString(printableB) + Log.w(TermDebug.LOG_TAG, "'" + Character.toString(printableB) + "' (" + Integer.toString(b) + ")"); } process(b); mProcessedCharCount++; } catch (Exception e) { - Log.e(Term.LOG_TAG, "Exception while processing character " + Log.e(TermDebug.LOG_TAG, "Exception while processing character " + Integer.toString(mProcessedCharCount) + " code " + Integer.toString(b), e); } @@ -915,8 +915,8 @@ private void selectGraphicRendition() { } else if (code == 49) { // set default background color mBackColor = mBackColor & 0x8; // color 0, but preserve underscore. } else { - if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) { - Log.w(Term.LOG_TAG, String.format("SGR unknown code %d", code)); + if (TermDebug.LOG_UNKNOWN_ESCAPE_SEQUENCES) { + Log.w(TermDebug.LOG_TAG, String.format("SGR unknown code %d", code)); } } } @@ -1072,21 +1072,21 @@ private int getArg(int index, int defaultValue) { } private void unimplementedSequence(byte b) { - if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) { + if (TermDebug.LOG_UNKNOWN_ESCAPE_SEQUENCES) { logError("unimplemented", b); } finishSequence(); } private void unknownSequence(byte b) { - if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) { + if (TermDebug.LOG_UNKNOWN_ESCAPE_SEQUENCES) { logError("unknown", b); } finishSequence(); } private void unknownParameter(int parameter) { - if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) { + if (TermDebug.LOG_UNKNOWN_ESCAPE_SEQUENCES) { StringBuilder buf = new StringBuilder(); buf.append("Unknown parameter"); buf.append(parameter); @@ -1095,7 +1095,7 @@ private void unknownParameter(int parameter) { } private void logError(String errorType, byte b) { - if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) { + if (TermDebug.LOG_UNKNOWN_ESCAPE_SEQUENCES) { StringBuilder buf = new StringBuilder(); buf.append(errorType); buf.append(" sequence "); @@ -1122,8 +1122,8 @@ private void logError(String errorType, byte b) { } private void logError(String error) { - if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) { - Log.e(Term.LOG_TAG, error); + if (TermDebug.LOG_UNKNOWN_ESCAPE_SEQUENCES) { + Log.e(TermDebug.LOG_TAG, error); } finishSequence(); } From 972a3497781451edde73899969d5ebbe61187c0a Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Sat, 27 Aug 2011 12:20:44 -0700 Subject: [PATCH 082/847] For multisession support, we eventually want to keep the terminal sessions in the service instead of tying them to the activity. This implies that we have to stop holding SharedPreferences objects in all of the classes tied to the session. To this end, introduce a new TermSettings class, which is responsible for reading SharedPreferences and holding the current value of config settings. --- .../androidterm/util/TermSettings.java | 170 ++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 src/jackpal/androidterm/util/TermSettings.java diff --git a/src/jackpal/androidterm/util/TermSettings.java b/src/jackpal/androidterm/util/TermSettings.java new file mode 100644 index 000000000..f366e7f99 --- /dev/null +++ b/src/jackpal/androidterm/util/TermSettings.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jackpal.androidterm.util; + +import android.content.SharedPreferences; +import android.view.KeyEvent; + +/** + * Terminal emulator settings + */ +public class TermSettings { + private SharedPreferences mPrefs; + + private int mStatusBar = 0; + private int mCursorStyle = 0; + private int mCursorBlink = 0; + private int mFontSize = 9; + private int mColorId = 2; + private int mControlKeyId = 5; // Default to Volume Down + private int mFnKeyId = 4; // Default to Volume Up + private int mUseCookedIME = 0; + private String mShell; + private String mInitialCommand; + + private static final String STATUSBAR_KEY = "statusbar"; + private static final String CURSORSTYLE_KEY = "cursorstyle"; + private static final String CURSORBLINK_KEY = "cursorblink"; + private static final String FONTSIZE_KEY = "fontsize"; + private static final String COLOR_KEY = "color"; + private static final String CONTROLKEY_KEY = "controlkey"; + private static final String FNKEY_KEY = "fnkey"; + private static final String IME_KEY = "ime"; + private static final String SHELL_KEY = "shell"; + private static final String INITIALCOMMAND_KEY = "initialcommand"; + + public static final int WHITE = 0xffffffff; + public static final int BLACK = 0xff000000; + public static final int BLUE = 0xff344ebd; + public static final int GREEN = 0xff00ff00; + public static final int AMBER = 0xffffb651; + public static final int RED = 0xffff0113; + + public static final int[][] COLOR_SCHEMES = {{BLACK, WHITE}, {WHITE, BLACK}, {WHITE, BLUE}, {GREEN, BLACK}, {AMBER, BLACK}, {RED, BLACK}}; + + /** An integer not in the range of real key codes. */ + public static final int KEYCODE_NONE = -1; + + public static final int CONTROL_KEY_ID_NONE = 7; + public static final int[] CONTROL_KEY_SCHEMES = { + KeyEvent.KEYCODE_DPAD_CENTER, + KeyEvent.KEYCODE_AT, + KeyEvent.KEYCODE_ALT_LEFT, + KeyEvent.KEYCODE_ALT_RIGHT, + KeyEvent.KEYCODE_VOLUME_UP, + KeyEvent.KEYCODE_VOLUME_DOWN, + KeyEvent.KEYCODE_CAMERA, + KEYCODE_NONE + }; + + public static final int FN_KEY_ID_NONE = 7; + public static final int[] FN_KEY_SCHEMES = { + KeyEvent.KEYCODE_DPAD_CENTER, + KeyEvent.KEYCODE_AT, + KeyEvent.KEYCODE_ALT_LEFT, + KeyEvent.KEYCODE_ALT_RIGHT, + KeyEvent.KEYCODE_VOLUME_UP, + KeyEvent.KEYCODE_VOLUME_DOWN, + KeyEvent.KEYCODE_CAMERA, + KEYCODE_NONE + }; + + public TermSettings(SharedPreferences prefs) { + readPrefs(prefs); + } + + public void readPrefs(SharedPreferences prefs) { + mPrefs = prefs; + mStatusBar = readIntPref(STATUSBAR_KEY, mStatusBar, 1); + // mCursorStyle = readIntPref(CURSORSTYLE_KEY, mCursorStyle, 2); + // mCursorBlink = readIntPref(CURSORBLINK_KEY, mCursorBlink, 1); + mFontSize = readIntPref(FONTSIZE_KEY, mFontSize, 20); + mColorId = readIntPref(COLOR_KEY, mColorId, COLOR_SCHEMES.length - 1); + mControlKeyId = readIntPref(CONTROLKEY_KEY, mControlKeyId, + CONTROL_KEY_SCHEMES.length - 1); + mFnKeyId = readIntPref(FNKEY_KEY, mFnKeyId, + FN_KEY_SCHEMES.length - 1); + mUseCookedIME = readIntPref(IME_KEY, mUseCookedIME, 1); + mShell = readStringPref(SHELL_KEY, mShell); + mInitialCommand = readStringPref(INITIALCOMMAND_KEY, mInitialCommand); + mPrefs = null; // we leak a Context if we hold on to this + } + + private int readIntPref(String key, int defaultValue, int maxValue) { + int val; + try { + val = Integer.parseInt( + mPrefs.getString(key, Integer.toString(defaultValue))); + } catch (NumberFormatException e) { + val = defaultValue; + } + val = Math.max(0, Math.min(val, maxValue)); + return val; + } + + private String readStringPref(String key, String defaultValue) { + return mPrefs.getString(key, defaultValue); + } + + public boolean showStatusBar() { + return (mStatusBar != 0); + } + + public int getCursorStyle() { + return mCursorStyle; + } + + public int getCursorBlink() { + return mCursorBlink; + } + + public int getFontSize() { + return mFontSize; + } + + public int[] getColorScheme() { + return COLOR_SCHEMES[mColorId]; + } + + public int getControlKeyId() { + return mControlKeyId; + } + + public int getFnKeyId() { + return mFnKeyId; + } + + public int getControlKeyCode() { + return CONTROL_KEY_SCHEMES[mControlKeyId]; + } + + public int getFnKeyCode() { + return FN_KEY_SCHEMES[mFnKeyId]; + } + + public boolean useCookedIME() { + return (mUseCookedIME != 0); + } + + public String getShell() { + return mShell; + } + + public String getInitialCommand() { + return mInitialCommand; + } +} From be18807725cddc0f6337a7771b01501a0881e886 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Sat, 27 Aug 2011 12:24:29 -0700 Subject: [PATCH 083/847] Modify the activity and view to use the new TermSettings class. --- src/jackpal/androidterm/EmulatorView.java | 25 +++- src/jackpal/androidterm/Term.java | 153 ++-------------------- 2 files changed, 31 insertions(+), 147 deletions(-) diff --git a/src/jackpal/androidterm/EmulatorView.java b/src/jackpal/androidterm/EmulatorView.java index 008063b1c..c87ec4711 100644 --- a/src/jackpal/androidterm/EmulatorView.java +++ b/src/jackpal/androidterm/EmulatorView.java @@ -56,6 +56,7 @@ import jackpal.androidterm.session.TerminalEmulator; import jackpal.androidterm.session.TranscriptScreen; import jackpal.androidterm.util.ByteQueue; +import jackpal.androidterm.util.TermSettings; /** * A view on a transcript and a terminal emulator. Displays the text of the @@ -66,6 +67,7 @@ public class EmulatorView extends View implements GestureDetector.OnGestureListe private final String TAG = "EmulatorView"; private final boolean LOG_KEY_EVENTS = TermDebug.DEBUG && false; + private TermSettings mSettings; private Term mTerm; /** @@ -275,14 +277,23 @@ public void onPause() { } } + public void updatePrefs(TermSettings settings, DisplayMetrics metrics) { + mSettings = settings; + setTextSize((int) (mSettings.getFontSize() * metrics.density)); + setCursorStyle(mSettings.getCursorStyle(), mSettings.getCursorBlink()); + setUseCookedIME(mSettings.useCookedIME()); + setColors(); + } + public void register(Term term, TermKeyListener listener) { mTerm = term; mKeyListener = listener; } - public void setColors(int foreground, int background) { - mForeground = foreground; - mBackground = background; + public void setColors() { + int[] scheme = mSettings.getColorScheme(); + mForeground = scheme[0]; + mBackground = scheme[1]; updateText(); } @@ -635,8 +646,8 @@ public void initialize(FileDescriptor termFd, FileOutputStream termOut) { mTermOut = termOut; mTermFd = termFd; mTextSize = 10; - mForeground = Term.WHITE; - mBackground = Term.BLACK; + mForeground = TermSettings.WHITE; + mBackground = TermSettings.BLACK; updateText(); mTermIn = new FileInputStream(mTermFd); mReceiveBuffer = new byte[4 * 1024]; @@ -867,7 +878,7 @@ public boolean onKeyUp(int keyCode, KeyEvent event) { private boolean handleControlKey(int keyCode, boolean down) { - if (keyCode == mTerm.getControlKeyCode()) { + if (keyCode == mSettings.getControlKeyCode()) { if (LOG_KEY_EVENTS) { Log.w(TAG, "handleControlKey " + keyCode); } @@ -878,7 +889,7 @@ private boolean handleControlKey(int keyCode, boolean down) { } private boolean handleFnKey(int keyCode, boolean down) { - if (keyCode == mTerm.getFnKeyCode()) { + if (keyCode == mSettings.getFnKeyCode()) { if (LOG_KEY_EVENTS) { Log.w(TAG, "handleFnKey " + keyCode); } diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index 99237052e..076706b13 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -41,7 +41,6 @@ import android.util.Log; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; -import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; @@ -50,6 +49,8 @@ import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; +import jackpal.androidterm.util.TermSettings; + /** * A terminal emulator activity. */ @@ -87,75 +88,14 @@ public class Term extends Activity { */ private static final int EMULATOR_VIEW = R.id.emulatorView; - private int mStatusBar = 0; - private int mCursorStyle = 0; - private int mCursorBlink = 0; - private int mFontSize = 9; - private int mColorId = 2; - private int mControlKeyId = 5; // Default to Volume Down - private int mFnKeyId = 4; // Default to Volume Up - private int mUseCookedIME = 0; - - private static final String STATUSBAR_KEY = "statusbar"; - private static final String CURSORSTYLE_KEY = "cursorstyle"; - private static final String CURSORBLINK_KEY = "cursorblink"; - private static final String FONTSIZE_KEY = "fontsize"; - private static final String COLOR_KEY = "color"; - private static final String CONTROLKEY_KEY = "controlkey"; - private static final String FNKEY_KEY = "fnkey"; - private static final String IME_KEY = "ime"; - private static final String SHELL_KEY = "shell"; - private static final String INITIALCOMMAND_KEY = "initialcommand"; - - public static final int WHITE = 0xffffffff; - public static final int BLACK = 0xff000000; - public static final int BLUE = 0xff344ebd; - public static final int GREEN = 0xff00ff00; - public static final int AMBER = 0xffffb651; - public static final int RED = 0xffff0113; - - private static final int[][] COLOR_SCHEMES = { - {BLACK, WHITE}, {WHITE, BLACK}, {WHITE, BLUE}, {GREEN, BLACK}, {AMBER, BLACK}, {RED, BLACK}}; - - private static final int CONTROL_KEY_ID_NONE = 7; - /** An integer not in the range of real key codes. */ - private static final int KEYCODE_NONE = -1; - - private static final int[] CONTROL_KEY_SCHEMES = { - KeyEvent.KEYCODE_DPAD_CENTER, - KeyEvent.KEYCODE_AT, - KeyEvent.KEYCODE_ALT_LEFT, - KeyEvent.KEYCODE_ALT_RIGHT, - KeyEvent.KEYCODE_VOLUME_UP, - KeyEvent.KEYCODE_VOLUME_DOWN, - KeyEvent.KEYCODE_CAMERA, - KEYCODE_NONE - }; - - private static final int FN_KEY_ID_NONE = 7; - - private static final int[] FN_KEY_SCHEMES = { - KeyEvent.KEYCODE_DPAD_CENTER, - KeyEvent.KEYCODE_AT, - KeyEvent.KEYCODE_ALT_LEFT, - KeyEvent.KEYCODE_ALT_RIGHT, - KeyEvent.KEYCODE_VOLUME_UP, - KeyEvent.KEYCODE_VOLUME_DOWN, - KeyEvent.KEYCODE_CAMERA, - KEYCODE_NONE - }; - - private int mControlKeyCode; - private int mFnKeyCode; - private final static String DEFAULT_SHELL = "/system/bin/sh -"; - private String mShell; + private String mInitialCommand; private final static String DEFAULT_INITIAL_COMMAND = "export PATH=/data/local/bin:$PATH"; - private String mInitialCommand; private SharedPreferences mPrefs; + private TermSettings mSettings; private final static int SELECT_TEXT_ID = 0; private final static int COPY_ALL_ID = 1; @@ -174,7 +114,7 @@ public void onCreate(Bundle icicle) { super.onCreate(icicle); Log.e(TermDebug.LOG_TAG, "onCreate"); mPrefs = PreferenceManager.getDefaultSharedPreferences(this); - readPrefs(); + mSettings = new TermSettings(mPrefs); TSIntent = new Intent(this, TermService.class); startService(TSIntent); @@ -259,6 +199,7 @@ public void run() { /* Check whether we've received an initial command from the * launching application */ + mInitialCommand = mSettings.getInitialCommand(); String iInitialCommand = getIntent().getStringExtra("jackpal.androidterm.iInitialCommand"); if (iInitialCommand != null) { if (mInitialCommand != null) { @@ -298,7 +239,7 @@ private void write(String data) { } private void createSubprocess(int[] processId) { - String shell = mShell; + String shell = mSettings.getShell(); if (shell == null || shell.equals("")) { shell = DEFAULT_SHELL; } @@ -363,55 +304,15 @@ private ArrayList parse(String cmd) { return result; } - private void readPrefs() { - mStatusBar = readIntPref(STATUSBAR_KEY, mStatusBar, 1); - // mCursorStyle = readIntPref(CURSORSTYLE_KEY, mCursorStyle, 2); - // mCursorBlink = readIntPref(CURSORBLINK_KEY, mCursorBlink, 1); - mFontSize = readIntPref(FONTSIZE_KEY, mFontSize, 20); - mColorId = readIntPref(COLOR_KEY, mColorId, COLOR_SCHEMES.length - 1); - mControlKeyId = readIntPref(CONTROLKEY_KEY, mControlKeyId, - CONTROL_KEY_SCHEMES.length - 1); - mFnKeyId = readIntPref(FNKEY_KEY, mFnKeyId, - FN_KEY_SCHEMES.length - 1); - mUseCookedIME = readIntPref(IME_KEY, mUseCookedIME, 1); - { - String newShell = readStringPref(SHELL_KEY, mShell); - if ((newShell == null) || ! newShell.equals(mShell)) { - if (mShell != null) { - Log.i(TermDebug.LOG_TAG, "New shell set. Restarting."); - restart(); - } - mShell = newShell; - } - } - { - String newInitialCommand = readStringPref(INITIALCOMMAND_KEY, - mInitialCommand); - if ((newInitialCommand == null) - || ! newInitialCommand.equals(mInitialCommand)) { - if (mInitialCommand != null) { - Log.i(TermDebug.LOG_TAG, "New initial command set. Restarting."); - restart(); - } - mInitialCommand = newInitialCommand; - } - } - } - private void updatePrefs() { DisplayMetrics metrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(metrics); - mEmulatorView.setTextSize((int) (mFontSize * metrics.density)); - mEmulatorView.setCursorStyle(mCursorStyle, mCursorBlink); - mEmulatorView.setUseCookedIME(mUseCookedIME != 0); - setColors(); - mControlKeyCode = CONTROL_KEY_SCHEMES[mControlKeyId]; - mFnKeyCode = FN_KEY_SCHEMES[mFnKeyId]; + mEmulatorView.updatePrefs(mSettings, metrics); { Window win = getWindow(); WindowManager.LayoutParams params = win.getAttributes(); final int FULLSCREEN = WindowManager.LayoutParams.FLAG_FULLSCREEN; - int desiredFlag = mStatusBar != 0 ? 0 : FULLSCREEN; + int desiredFlag = mSettings.showStatusBar() ? 0 : FULLSCREEN; if (desiredFlag != (params.flags & FULLSCREEN)) { if (mAlreadyStarted) { // Can't switch to/from fullscreen after @@ -424,34 +325,11 @@ private void updatePrefs() { } } - private int readIntPref(String key, int defaultValue, int maxValue) { - int val; - try { - val = Integer.parseInt( - mPrefs.getString(key, Integer.toString(defaultValue))); - } catch (NumberFormatException e) { - val = defaultValue; - } - val = Math.max(0, Math.min(val, maxValue)); - return val; - } - - private String readStringPref(String key, String defaultValue) { - return mPrefs.getString(key, defaultValue); - } - - public int getControlKeyCode() { - return mControlKeyCode; - } - - public int getFnKeyCode() { - return mFnKeyCode; - } - @Override public void onResume() { super.onResume(); - readPrefs(); + mPrefs = PreferenceManager.getDefaultSharedPreferences(this); + mSettings.readPrefs(mPrefs); updatePrefs(); mEmulatorView.onResume(); } @@ -555,11 +433,6 @@ private void doPreferences() { startActivity(new Intent(this, TermPreferences.class)); } - private void setColors() { - int[] scheme = COLOR_SCHEMES[mColorId]; - mEmulatorView.setColors(scheme[0], scheme[1]); - } - private void doResetTerminal() { restart(); } @@ -606,12 +479,12 @@ private void doDocumentKeys() { Resources r = getResources(); dialog.setTitle(r.getString(R.string.control_key_dialog_title)); dialog.setMessage( - formatMessage(mControlKeyId, CONTROL_KEY_ID_NONE, + formatMessage(mSettings.getControlKeyId(), TermSettings.CONTROL_KEY_ID_NONE, r, R.array.control_keys_short_names, R.string.control_key_dialog_control_text, R.string.control_key_dialog_control_disabled_text, "CTRLKEY") + "\n\n" + - formatMessage(mFnKeyId, FN_KEY_ID_NONE, + formatMessage(mSettings.getFnKeyId(), TermSettings.FN_KEY_ID_NONE, r, R.array.fn_keys_short_names, R.string.control_key_dialog_fn_text, R.string.control_key_dialog_fn_disabled_text, "FNKEY")); From bf017dbf1335689c53a9e65d6001806ec2a8819b Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Sat, 27 Aug 2011 12:29:49 -0700 Subject: [PATCH 084/847] Add TermSession class to represent a terminal session At the moment, the assumption that each terminal activity contains only one EmulatorView and hosts only one terminal session is fairly deeply embedded into the code. To help break this assumption, we introduce a new TermSession class, which holds the TerminalEmulator and TranscriptScreen for a session, and handles the launching and watching of the process and the setup of the I/O streams. --- .../androidterm/model/UpdateCallback.java | 24 ++ .../androidterm/session/TermSession.java | 257 ++++++++++++++++++ 2 files changed, 281 insertions(+) create mode 100644 src/jackpal/androidterm/model/UpdateCallback.java create mode 100644 src/jackpal/androidterm/session/TermSession.java diff --git a/src/jackpal/androidterm/model/UpdateCallback.java b/src/jackpal/androidterm/model/UpdateCallback.java new file mode 100644 index 000000000..8e668a2e6 --- /dev/null +++ b/src/jackpal/androidterm/model/UpdateCallback.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jackpal.androidterm.model; + +/** + * Callback to be invoked when updates to a TermSession's transcript occur. + */ +public interface UpdateCallback { + void onUpdate(); +} diff --git a/src/jackpal/androidterm/session/TermSession.java b/src/jackpal/androidterm/session/TermSession.java new file mode 100644 index 000000000..dd75583d6 --- /dev/null +++ b/src/jackpal/androidterm/session/TermSession.java @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jackpal.androidterm.session; + +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; + +import android.os.Handler; +import android.os.Message; +import android.util.Log; + +import jackpal.androidterm.Exec; +import jackpal.androidterm.TermDebug; +import jackpal.androidterm.model.UpdateCallback; +import jackpal.androidterm.util.ByteQueue; +import jackpal.androidterm.util.TermSettings; + +/** + * A terminal session, consisting of a TerminalEmulator, a TranscriptScreen, + * the PID of the process attached to the session, and the I/O streams used to + * talk to the process. + */ +public class TermSession { + private TermSettings mSettings; + private UpdateCallback mNotify; + + private int mProcId; + private FileDescriptor mTermFd; + private FileOutputStream mTermOut; + private FileInputStream mTermIn; + + private TranscriptScreen mTranscriptScreen; + private TerminalEmulator mEmulator; + + private Thread mPollingThread; + private ByteQueue mByteQueue; + private byte[] mReceiveBuffer; + + private static final int DEFAULT_COLUMNS = 80; + private static final int DEFAULT_ROWS = 24; + private static final String DEFAULT_SHELL = "/system/bin/sh -"; + private static final String DEFAULT_INITIAL_COMMAND = + "export PATH=/data/local/bin:$PATH"; + + // Number of rows in the transcript + private static final int TRANSCRIPT_ROWS = 10000; + + private static final int NEW_INPUT = 1; + + private Handler mMsgHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + if (msg.what == NEW_INPUT) { + readFromProcess(); + } + } + }; + + public TermSession(TermSettings settings, UpdateCallback notify, String initialCommand) { + mSettings = settings; + mNotify = notify; + + int[] processId = new int[1]; + + createSubprocess(processId); + mProcId = processId[0]; + mTermOut = new FileOutputStream(mTermFd); + mTermIn = new FileInputStream(mTermFd); + + mTranscriptScreen = new TranscriptScreen(DEFAULT_COLUMNS, TRANSCRIPT_ROWS, DEFAULT_ROWS, 0, 7); + mEmulator = new TerminalEmulator(mTranscriptScreen, DEFAULT_COLUMNS, DEFAULT_ROWS, mTermOut); + + Thread watcher = new Thread() { + @Override + public void run() { + Log.i(TermDebug.LOG_TAG, "waiting for: " + mProcId); + int result = Exec.waitFor(mProcId); + Log.i(TermDebug.LOG_TAG, "Subprocess exited: " + result); + mMsgHandler.sendEmptyMessage(result); + } + }; + watcher.setName("Process watcher"); + watcher.start(); + + mReceiveBuffer = new byte[4 * 1024]; + mByteQueue = new ByteQueue(4 * 1024); + + mPollingThread = new Thread() { + private byte[] mBuffer = new byte[4096]; + + @Override + public void run() { + try { + while(true) { + int read = mTermIn.read(mBuffer); + mByteQueue.write(mBuffer, 0, read); + mMsgHandler.sendMessage( + mMsgHandler.obtainMessage(NEW_INPUT)); + } + } catch (IOException e) { + } catch (InterruptedException e) { + } + } + }; + mPollingThread.setName("Input reader"); + mPollingThread.start(); + + sendInitialCommand(initialCommand); + } + + private void sendInitialCommand(String initialCommand) { + if (initialCommand == null || initialCommand.equals("")) { + initialCommand = DEFAULT_INITIAL_COMMAND; + } + if (initialCommand.length() > 0) { + write(initialCommand + '\r'); + } + } + + public void write(String data) { + try { + mTermOut.write(data.getBytes()); + mTermOut.flush(); + } catch (IOException e) { + // Ignore exception + // We don't really care if the receiver isn't listening. + // We just make a best effort to answer the query. + } + } + + private void createSubprocess(int[] processId) { + String shell = mSettings.getShell(); + if (shell == null || shell.equals("")) { + shell = DEFAULT_SHELL; + } + ArrayList args = parse(shell); + String arg0 = args.get(0); + String arg1 = null; + String arg2 = null; + if (args.size() >= 2) { + arg1 = args.get(1); + } + if (args.size() >= 3) { + arg2 = args.get(2); + } + mTermFd = Exec.createSubprocess(arg0, arg1, arg2, processId); + } + + private ArrayList parse(String cmd) { + final int PLAIN = 0; + final int WHITESPACE = 1; + final int INQUOTE = 2; + int state = WHITESPACE; + ArrayList result = new ArrayList(); + int cmdLen = cmd.length(); + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < cmdLen; i++) { + char c = cmd.charAt(i); + if (state == PLAIN) { + if (Character.isWhitespace(c)) { + result.add(builder.toString()); + builder.delete(0,builder.length()); + state = WHITESPACE; + } else if (c == '"') { + state = INQUOTE; + } else { + builder.append(c); + } + } else if (state == WHITESPACE) { + if (Character.isWhitespace(c)) { + // do nothing + } else if (c == '"') { + state = INQUOTE; + } else { + state = PLAIN; + builder.append(c); + } + } else if (state == INQUOTE) { + if (c == '\\') { + if (i + 1 < cmdLen) { + i += 1; + builder.append(cmd.charAt(i)); + } + } else if (c == '"') { + state = PLAIN; + } else { + builder.append(c); + } + } + } + if (builder.length() > 0) { + result.add(builder.toString()); + } + return result; + } + + public FileOutputStream getTermOut() { + return mTermOut; + } + + public TranscriptScreen getTranscriptScreen() { + return mTranscriptScreen; + } + + public TerminalEmulator getEmulator() { + return mEmulator; + } + + public void setUpdateCallback(UpdateCallback notify) { + mNotify = notify; + } + + public void updateSize(int columns, int rows) { + // Inform the attached pty of our new size: + Exec.setPtyWindowSize(mTermFd, rows, columns, 0, 0); + mEmulator.updateSize(columns, rows); + } + + /** + * Look for new input from the ptty, send it to the terminal emulator. + */ + private void readFromProcess() { + int bytesAvailable = mByteQueue.getBytesAvailable(); + int bytesToRead = Math.min(bytesAvailable, mReceiveBuffer.length); + try { + int bytesRead = mByteQueue.read(mReceiveBuffer, 0, bytesToRead); + mEmulator.append(mReceiveBuffer, 0, bytesRead); + } catch (InterruptedException e) { + } + + if (mNotify != null) { + mNotify.onUpdate(); + } + } + + public void finish() { + Exec.hangupProcessGroup(mProcId); + Exec.close(mTermFd); + } +} From 47cc8a2b6c56ea0214c59445852e63bbfcf030d6 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Sat, 27 Aug 2011 12:33:24 -0700 Subject: [PATCH 085/847] Use TermSession in activity and view instead of old terminal-handling code This completes the process of isolating the code for each terminal session, which should considerably simplify the task of adding support for multiple sessions. --- src/jackpal/androidterm/EmulatorView.java | 160 ++++------------- src/jackpal/androidterm/Term.java | 201 +++------------------- 2 files changed, 53 insertions(+), 308 deletions(-) diff --git a/src/jackpal/androidterm/EmulatorView.java b/src/jackpal/androidterm/EmulatorView.java index c87ec4711..0ac912a57 100644 --- a/src/jackpal/androidterm/EmulatorView.java +++ b/src/jackpal/androidterm/EmulatorView.java @@ -16,8 +16,6 @@ package jackpal.androidterm; -import java.io.FileDescriptor; -import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; @@ -35,7 +33,6 @@ import android.graphics.Typeface; import android.os.Bundle; import android.os.Handler; -import android.os.Message; import android.text.ClipboardManager; import android.util.AttributeSet; import android.util.DisplayMetrics; @@ -53,9 +50,10 @@ import android.view.inputmethod.InputConnection; import jackpal.androidterm.model.TextRenderer; +import jackpal.androidterm.model.UpdateCallback; import jackpal.androidterm.session.TerminalEmulator; +import jackpal.androidterm.session.TermSession; import jackpal.androidterm.session.TranscriptScreen; -import jackpal.androidterm.util.ByteQueue; import jackpal.androidterm.util.TermSettings; /** @@ -68,7 +66,6 @@ public class EmulatorView extends View implements GestureDetector.OnGestureListe private final boolean LOG_KEY_EVENTS = TermDebug.DEBUG && false; private TermSettings mSettings; - private Term mTerm; /** * We defer some initialization until we have been layed out in the view @@ -80,16 +77,13 @@ public class EmulatorView extends View implements GestureDetector.OnGestureListe private int mVisibleHeight; private Rect mVisibleRect = new Rect(); + private TermSession mTermSession; + /** * Our transcript. Contains the screen and the transcript. */ private TranscriptScreen mTranscriptScreen; - /** - * Number of rows in the transcript. - */ - private static final int TRANSCRIPT_ROWS = 10000; - /** * Total width of each character, in pixels */ @@ -160,28 +154,11 @@ public class EmulatorView extends View implements GestureDetector.OnGestureListe private int mLeftColumn; - private FileDescriptor mTermFd; /** * Used to receive data from the remote process. */ - private FileInputStream mTermIn; - private FileOutputStream mTermOut; - private ByteQueue mByteQueue; - - /** - * Used to temporarily hold data received from the remote process. Allocated - * once and used permanently to minimize heap thrashing. - */ - private byte[] mReceiveBuffer; - - /** - * Our private message id, which we use to receive new input from the - * remote process. - */ - private static final int UPDATE = 1; - private static final int SCREEN_CHECK_PERIOD = 1000; private static final int CURSOR_BLINK_PERIOD = 1000; @@ -223,12 +200,6 @@ public void run() { } }; - /** - * Thread that polls for input from the remote process - */ - - private Thread mPollingThread; - private GestureDetector mGestureDetector; private float mScrollRemainder; private TermKeyListener mKeyListener; @@ -238,21 +209,30 @@ public void run() { /** * Our message handler class. Implements a periodic callback. */ - private final Handler mHandler = new Handler() { - /** - * Handle the callback message. Call our enclosing class's update - * method. - * - * @param msg The callback message. - */ + private final Handler mHandler = new Handler(); + + /** + * Called by the TermSession when the contents of the view need updating + */ + private UpdateCallback mUpdateNotify = new UpdateCallback() { @Override - public void handleMessage(Message msg) { - if (msg.what == UPDATE) { - update(); + public void onUpdate() { + if ( mIsSelectingText ) { + int rowShift = mEmulator.getScrollCounter(); + mSelY1 -= rowShift; + mSelY2 -= rowShift; + mSelYAnchor -= rowShift; } + mEmulator.clearScrollCounter(); + ensureCursorVisible(); + invalidate(); } }; + public UpdateCallback getUpdateCallback() { + return mUpdateNotify; + } + public EmulatorView(Context context) { super(context); commonConstructor(); @@ -285,11 +265,6 @@ public void updatePrefs(TermSettings settings, DisplayMetrics metrics) { setColors(); } - public void register(Term term, TermKeyListener listener) { - mTerm = term; - mKeyListener = listener; - } - public void setColors() { int[] scheme = mSettings.getColorScheme(); mForeground = scheme[0]; @@ -639,40 +614,19 @@ protected int computeVerticalScrollOffset() { /** * Call this to initialize the view. * - * @param termFd the file descriptor - * @param termOut the output stream for the pseudo-teletype + * @param session The terminal session this view will be displaying */ - public void initialize(FileDescriptor termFd, FileOutputStream termOut) { - mTermOut = termOut; - mTermFd = termFd; + public void initialize(TermSession session) { + mTermSession = session; + mTranscriptScreen = session.getTranscriptScreen(); + mEmulator = session.getEmulator(); + mTermOut = session.getTermOut(); + + mKeyListener = new TermKeyListener(); mTextSize = 10; mForeground = TermSettings.WHITE; mBackground = TermSettings.BLACK; updateText(); - mTermIn = new FileInputStream(mTermFd); - mReceiveBuffer = new byte[4 * 1024]; - mByteQueue = new ByteQueue(4 * 1024); - } - - /** - * Accept a sequence of bytes (typically from the pseudo-tty) and process - * them. - * - * @param buffer a byte array containing bytes to be processed - * @param base the index of the first byte in the buffer to process - * @param length the number of bytes to process - */ - public void append(byte[] buffer, int base, int length) { - mEmulator.append(buffer, base, length); - if ( mIsSelectingText ) { - int rowShift = mEmulator.getScrollCounter(); - mSelY1 -= rowShift; - mSelY2 -= rowShift; - mSelYAnchor -= rowShift; - } - mEmulator.clearScrollCounter(); - ensureCursorVisible(); - invalidate(); } /** @@ -926,29 +880,6 @@ protected void onSizeChanged(int w, int h, int oldw, int oldh) { mKnownSize = true; } updateSize(false); - if (!oldKnownSize) { - // Set up a thread to read input from the - // pseudo-teletype: - - mPollingThread = new Thread(new Runnable() { - - public void run() { - try { - while(true) { - int read = mTermIn.read(mBuffer); - mByteQueue.write(mBuffer, 0, read); - mHandler.sendMessage( - mHandler.obtainMessage(UPDATE)); - } - } catch (IOException e) { - } catch (InterruptedException e) { - } - } - private byte[] mBuffer = new byte[4096]; - }); - mPollingThread.setName("Input reader"); - mPollingThread.start(); - } } private void updateSize(int w, int h) { @@ -956,19 +887,7 @@ private void updateSize(int w, int h) { mRows = Math.max(1, h / mCharacterHeight); mVisibleColumns = mVisibleWidth / mCharacterWidth; - // Inform the attached pty of our new size: - Exec.setPtyWindowSize(mTermFd, mRows, mColumns, w, h); - - - if (mTranscriptScreen != null) { - mEmulator.updateSize(mColumns, mRows); - } else { - mTranscriptScreen = - new TranscriptScreen(mColumns, TRANSCRIPT_ROWS, mRows, 0, 7); - mEmulator = - new TerminalEmulator(mTranscriptScreen, mColumns, mRows, - mTermOut); - } + mTermSession.updateSize(mColumns, mRows); // Reset our paging: mTopRow = 0; @@ -977,7 +896,7 @@ private void updateSize(int w, int h) { invalidate(); } - void updateSize(boolean force) { + public void updateSize(boolean force) { if (mKnownSize) { getWindowVisibleDisplayFrame(mVisibleRect); int w = mVisibleRect.width(); @@ -991,19 +910,6 @@ void updateSize(boolean force) { } } - /** - * Look for new input from the ptty, send it to the terminal emulator. - */ - private void update() { - int bytesAvailable = mByteQueue.getBytesAvailable(); - int bytesToRead = Math.min(bytesAvailable, mReceiveBuffer.length); - try { - int bytesRead = mByteQueue.read(mReceiveBuffer, 0, bytesToRead); - append(mReceiveBuffer, 0, bytesRead); - } catch (InterruptedException e) { - } - } - @Override protected void onDraw(Canvas canvas) { updateSize(false); diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index 076706b13..0e4a27489 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -16,11 +16,7 @@ package jackpal.androidterm; -import java.io.FileDescriptor; -import java.io.FileOutputStream; -import java.io.IOException; import java.io.UnsupportedEncodingException; -import java.util.ArrayList; import android.app.Activity; import android.app.AlertDialog; @@ -32,8 +28,6 @@ import android.net.Uri; import android.net.wifi.WifiManager; import android.os.Bundle; -import android.os.Handler; -import android.os.Message; import android.os.PowerManager; import android.preference.PreferenceManager; import android.text.ClipboardManager; @@ -49,6 +43,7 @@ import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; +import jackpal.androidterm.session.TermSession; import jackpal.androidterm.util.TermSettings; /** @@ -61,38 +56,14 @@ public class Term extends Activity { */ private EmulatorView mEmulatorView; - /** - * The pseudo-teletype (pty) file descriptor that we use to communicate with - * another process, typically a shell. - */ - private FileDescriptor mTermFd; - - /** - * Used to send data to the remote process. - */ - private FileOutputStream mTermOut; - - /** - * The process ID of the remote process. - */ - private int mProcId = 0; - - /** - * A key listener that tracks the modifier keys and allows the full ASCII - * character set to be entered. - */ - private TermKeyListener mKeyListener; - /** * The name of our emulator view in the view resource. */ private static final int EMULATOR_VIEW = R.id.emulatorView; - private final static String DEFAULT_SHELL = "/system/bin/sh -"; + private TermSession mTermSession; private String mInitialCommand; - private final static String DEFAULT_INITIAL_COMMAND = - "export PATH=/data/local/bin:$PATH"; private SharedPreferences mPrefs; private TermSettings mSettings; @@ -123,18 +94,29 @@ public void onCreate(Bundle icicle) { mEmulatorView = (EmulatorView) findViewById(EMULATOR_VIEW); + /* Check whether we've received an initial command from the + * launching application + */ + mInitialCommand = mSettings.getInitialCommand(); + String iInitialCommand = getIntent().getStringExtra("jackpal.androidterm.iInitialCommand"); + if (iInitialCommand != null) { + if (mInitialCommand != null) { + mInitialCommand += "\r" + iInitialCommand; + } else { + mInitialCommand = iInitialCommand; + } + } + mTermSession = new TermSession(mSettings, mEmulatorView.getUpdateCallback(), mInitialCommand); + + mEmulatorView.initialize(mTermSession); + DisplayMetrics metrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(metrics); mEmulatorView.setScaledDensity(metrics.scaledDensity); - startListening(); - - mKeyListener = new TermKeyListener(); - mEmulatorView.setFocusable(true); mEmulatorView.setFocusableInTouchMode(true); mEmulatorView.requestFocus(); - mEmulatorView.register(this, mKeyListener); registerForContextMenu(mEmulatorView); @@ -150,14 +132,7 @@ public void onCreate(Bundle icicle) { @Override public void onDestroy() { super.onDestroy(); - if (mProcId != 0) { - Exec.hangupProcessGroup(mProcId); - mProcId = 0; - } - if (mTermFd != null) { - Exec.close(mTermFd); - mTermFd = null; - } + mTermSession.finish(); if (mWakeLock.isHeld()) { mWakeLock.release(); } @@ -167,143 +142,11 @@ public void onDestroy() { stopService(TSIntent); } - private void startListening() { - int[] processId = new int[1]; - - createSubprocess(processId); - mProcId = processId[0]; - - final Handler handler = new Handler() { - @Override - public void handleMessage(Message msg) { - } - }; - - Runnable watchForDeath = new Runnable() { - - public void run() { - Log.i(TermDebug.LOG_TAG, "waiting for: " + mProcId); - int result = Exec.waitFor(mProcId); - Log.i(TermDebug.LOG_TAG, "Subprocess exited: " + result); - handler.sendEmptyMessage(result); - } - - }; - Thread watcher = new Thread(watchForDeath); - watcher.start(); - - mTermOut = new FileOutputStream(mTermFd); - - mEmulatorView.initialize(mTermFd, mTermOut); - - /* Check whether we've received an initial command from the - * launching application - */ - mInitialCommand = mSettings.getInitialCommand(); - String iInitialCommand = getIntent().getStringExtra("jackpal.androidterm.iInitialCommand"); - if (iInitialCommand != null) { - if (mInitialCommand != null) { - mInitialCommand += "\r" + iInitialCommand; - } else { - mInitialCommand = iInitialCommand; - } - } - - sendInitialCommand(); - } - - private void sendInitialCommand() { - String initialCommand = mInitialCommand; - if (initialCommand == null || initialCommand.equals("")) { - initialCommand = DEFAULT_INITIAL_COMMAND; - } - if (initialCommand.length() > 0) { - write(initialCommand + '\r'); - } - } - private void restart() { startActivity(getIntent()); finish(); } - private void write(String data) { - try { - mTermOut.write(data.getBytes()); - mTermOut.flush(); - } catch (IOException e) { - // Ignore exception - // We don't really care if the receiver isn't listening. - // We just make a best effort to answer the query. - } - } - - private void createSubprocess(int[] processId) { - String shell = mSettings.getShell(); - if (shell == null || shell.equals("")) { - shell = DEFAULT_SHELL; - } - ArrayList args = parse(shell); - String arg0 = args.get(0); - String arg1 = null; - String arg2 = null; - if (args.size() >= 2) { - arg1 = args.get(1); - } - if (args.size() >= 3) { - arg2 = args.get(2); - } - mTermFd = Exec.createSubprocess(arg0, arg1, arg2, processId); - } - - private ArrayList parse(String cmd) { - final int PLAIN = 0; - final int WHITESPACE = 1; - final int INQUOTE = 2; - int state = WHITESPACE; - ArrayList result = new ArrayList(); - int cmdLen = cmd.length(); - StringBuilder builder = new StringBuilder(); - for (int i = 0; i < cmdLen; i++) { - char c = cmd.charAt(i); - if (state == PLAIN) { - if (Character.isWhitespace(c)) { - result.add(builder.toString()); - builder.delete(0,builder.length()); - state = WHITESPACE; - } else if (c == '"') { - state = INQUOTE; - } else { - builder.append(c); - } - } else if (state == WHITESPACE) { - if (Character.isWhitespace(c)) { - // do nothing - } else if (c == '"') { - state = INQUOTE; - } else { - state = PLAIN; - builder.append(c); - } - } else if (state == INQUOTE) { - if (c == '\\') { - if (i + 1 < cmdLen) { - i += 1; - builder.append(cmd.charAt(i)); - } - } else if (c == '"') { - state = PLAIN; - } else { - builder.append(c); - } - } - } - if (builder.length() > 0) { - result.add(builder.toString()); - } - return result; - } - private void updatePrefs() { DisplayMetrics metrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(metrics); @@ -467,11 +310,7 @@ private void doPaste() { Log.e(TermDebug.LOG_TAG, "UTF-8 encoding not found."); return; } - try { - mTermOut.write(utf8); - } catch (IOException e) { - Log.e(TermDebug.LOG_TAG, "could not write paste text to terminal."); - } + mTermSession.write(paste.toString()); } private void doDocumentKeys() { From 4ffcbfddc7b8192b73ba4da6dea6560e05676fc0 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Sat, 27 Aug 2011 12:37:47 -0700 Subject: [PATCH 086/847] Throw away TranscriptScreen backing store on session finish Android's InputMethodManager seems to hold a reference to a finished activity object for a while after the activity is destroyed. This keeps everything the activity holds a reference to in memory, which in our case includes a fairly significant chunk of memory: the backing store to the TranscriptScreen. Throw this away when it's not needed, to reduce the memory impact of this behavior. --- src/jackpal/androidterm/session/TermSession.java | 1 + .../androidterm/session/TranscriptScreen.java | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/src/jackpal/androidterm/session/TermSession.java b/src/jackpal/androidterm/session/TermSession.java index dd75583d6..46a824a10 100644 --- a/src/jackpal/androidterm/session/TermSession.java +++ b/src/jackpal/androidterm/session/TermSession.java @@ -253,5 +253,6 @@ private void readFromProcess() { public void finish() { Exec.hangupProcessGroup(mProcId); Exec.close(mTermFd); + mTranscriptScreen.finish(); } } diff --git a/src/jackpal/androidterm/session/TranscriptScreen.java b/src/jackpal/androidterm/session/TranscriptScreen.java index c79e4030f..b725893c0 100644 --- a/src/jackpal/androidterm/session/TranscriptScreen.java +++ b/src/jackpal/androidterm/session/TranscriptScreen.java @@ -115,6 +115,19 @@ private void init(int columns, int totalRows, int screenRows, int foreColor, int consistencyCheck(); } + public void finish() { + /* + * The Android InputMethodService will sometimes hold a reference to + * us for a while after the activity closes, which is expensive because + * it means holding on to the now-useless mData array. Explicitly + * get rid of our references to this data to help keep the amount of + * memory being leaked down. + */ + mData = null; + mRowBuffer = null; + mLineWrap = null; + } + /** * Convert a row value from the public external coordinate system to our * internal private coordinate system. External coordinate system: From f44119c2118cab693fd0bd67c64e13e0817c9279 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Sat, 27 Aug 2011 13:22:21 -0700 Subject: [PATCH 087/847] Don't accept input from process after TranscriptScreen is finished. This prevents a bunch of noise in the logs. --- src/jackpal/androidterm/session/TermSession.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/jackpal/androidterm/session/TermSession.java b/src/jackpal/androidterm/session/TermSession.java index 46a824a10..b5d567c05 100644 --- a/src/jackpal/androidterm/session/TermSession.java +++ b/src/jackpal/androidterm/session/TermSession.java @@ -64,9 +64,13 @@ public class TermSession { private static final int NEW_INPUT = 1; + private boolean mIsRunning = false; private Handler mMsgHandler = new Handler() { @Override public void handleMessage(Message msg) { + if (!mIsRunning) { + return; + } if (msg.what == NEW_INPUT) { readFromProcess(); } @@ -87,6 +91,8 @@ public TermSession(TermSettings settings, UpdateCallback notify, String initialC mTranscriptScreen = new TranscriptScreen(DEFAULT_COLUMNS, TRANSCRIPT_ROWS, DEFAULT_ROWS, 0, 7); mEmulator = new TerminalEmulator(mTranscriptScreen, DEFAULT_COLUMNS, DEFAULT_ROWS, mTermOut); + mIsRunning = true; + Thread watcher = new Thread() { @Override public void run() { @@ -253,6 +259,7 @@ private void readFromProcess() { public void finish() { Exec.hangupProcessGroup(mProcId); Exec.close(mTermFd); + mIsRunning = false; mTranscriptScreen.finish(); } } From 5f4114849ba84e8bbab65ca8076915173bb95f7d Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Sat, 27 Aug 2011 13:23:52 -0700 Subject: [PATCH 088/847] Support multiple sessions per terminal activity. This patch does the following: * Replace the EmulatorView widget in the activity layout with a TermViewFlipper, which is a ViewFlipper with a couple of added conveniences for dealing with EmulatorViews (e.g. pausing/resuming them when changing the view). * Add an ArrayList in the service to hold TermSessions, and make the activity bind to the service so that it can retrieve the list. This isn't strictly necessary for multisession support, but allows us to decouple the activity from the sessions, which will (in the future) allow us to restart or even close the activity without losing running sessions. * Now that EmulatorViews aren't being created automatically when the layout is inflated, but are instead created by hand, move a bunch of initialization stuff to the EmulatorView constructor. * Make the input reader threads for each session exit on EOF. These threads hold a reference to their TermSession objects, which would leak otherwise. For now, one EmulatorView and TermSession are created as soon as the activity binds to the service, if none exist; no UI to create additional sessions is included. --- res/layout/term_activity.xml | 14 +- src/jackpal/androidterm/EmulatorView.java | 44 ++--- src/jackpal/androidterm/Term.java | 159 +++++++++++++----- src/jackpal/androidterm/TermService.java | 26 ++- src/jackpal/androidterm/TermViewFlipper.java | 117 +++++++++++++ .../androidterm/session/TermSession.java | 8 + .../androidterm/session/TerminalEmulator.java | 3 - 7 files changed, 286 insertions(+), 85 deletions(-) create mode 100644 src/jackpal/androidterm/TermViewFlipper.java diff --git a/res/layout/term_activity.xml b/res/layout/term_activity.xml index 4c3da6dbe..b84a51a8c 100644 --- a/res/layout/term_activity.xml +++ b/res/layout/term_activity.xml @@ -16,15 +16,9 @@ */ --> - - - - - + android:background="@android:color/black" + /> diff --git a/src/jackpal/androidterm/EmulatorView.java b/src/jackpal/androidterm/EmulatorView.java index 0ac912a57..e02e52da8 100644 --- a/src/jackpal/androidterm/EmulatorView.java +++ b/src/jackpal/androidterm/EmulatorView.java @@ -167,6 +167,8 @@ public class EmulatorView extends View implements GestureDetector.OnGestureListe private boolean mIsSelectingText = false; + private float mDensity; + private float mScaledDensity; private static final int SELECT_TEXT_OFFSET_Y = -40; private int mSelXAnchor = -1; @@ -233,13 +235,15 @@ public UpdateCallback getUpdateCallback() { return mUpdateNotify; } - public EmulatorView(Context context) { + public EmulatorView(Context context, TermSession session, TermViewFlipper viewFlipper, DisplayMetrics metrics) { super(context); - commonConstructor(); + commonConstructor(session, viewFlipper); + setDensity(metrics); } - public void setScaledDensity(float scaledDensity) { - mScaledDensity = scaledDensity; + public void setDensity(DisplayMetrics metrics) { + mDensity = metrics.density; + mScaledDensity = metrics.scaledDensity; } public void onResume() { @@ -257,9 +261,9 @@ public void onPause() { } } - public void updatePrefs(TermSettings settings, DisplayMetrics metrics) { + public void updatePrefs(TermSettings settings) { mSettings = settings; - setTextSize((int) (mSettings.getFontSize() * metrics.density)); + setTextSize((int) (mSettings.getFontSize() * mDensity)); setCursorStyle(mSettings.getCursorStyle(), mSettings.getCursorBlink()); setUseCookedIME(mSettings.useCookedIME()); setColors(); @@ -272,10 +276,6 @@ public void setColors() { updateText(); } - public String getTranscriptText() { - return mEmulator.getTranscriptText(); - } - public void resetTerminal() { mEmulator.reset(); invalidate(); @@ -570,21 +570,7 @@ public boolean getKeypadApplicationMode() { return mEmulator.getKeypadApplicationMode(); } - public EmulatorView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public EmulatorView(Context context, AttributeSet attrs, - int defStyle) { - super(context, attrs, defStyle); - // TypedArray a = - // context.obtainStyledAttributes(android.R.styleable.View); - // initializeScrollbars(a); - // a.recycle(); - commonConstructor(); - } - - private void commonConstructor() { + private void commonConstructor(TermSession session, TermViewFlipper viewFlipper) { mTextRenderer = null; mCursorPaint = new Paint(); mCursorPaint.setARGB(255,128,128,128); @@ -594,6 +580,10 @@ private void commonConstructor() { mGestureDetector = new GestureDetector(this); // mGestureDetector.setIsLongpressEnabled(false); setVerticalScrollBarEnabled(true); + setFocusable(true); + setFocusableInTouchMode(true); + + initialize(session, viewFlipper); } @Override @@ -616,7 +606,7 @@ protected int computeVerticalScrollOffset() { * * @param session The terminal session this view will be displaying */ - public void initialize(TermSession session) { + private void initialize(TermSession session, TermViewFlipper viewFlipper) { mTermSession = session; mTranscriptScreen = session.getTranscriptScreen(); mEmulator = session.getEmulator(); @@ -627,6 +617,8 @@ public void initialize(TermSession session) { mForeground = TermSettings.WHITE; mBackground = TermSettings.BLACK; updateText(); + + requestFocus(); } /** diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index 0e4a27489..f912dcaef 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -17,17 +17,21 @@ package jackpal.androidterm; import java.io.UnsupportedEncodingException; +import java.util.ArrayList; import android.app.Activity; import android.app.AlertDialog; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; +import android.content.ServiceConnection; import android.content.res.Configuration; import android.content.res.Resources; import android.net.Uri; import android.net.wifi.WifiManager; import android.os.Bundle; +import android.os.IBinder; import android.os.PowerManager; import android.preference.PreferenceManager; import android.text.ClipboardManager; @@ -35,6 +39,7 @@ import android.util.Log; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; +import android.view.Gravity; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; @@ -42,6 +47,7 @@ import android.view.Window; import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; +import android.widget.FrameLayout; import jackpal.androidterm.session.TermSession; import jackpal.androidterm.util.TermSettings; @@ -52,18 +58,16 @@ public class Term extends Activity { /** - * Our main view. Displays the emulated terminal screen. + * The ViewFlipper which holds the collection of EmulatorView widgets. */ - private EmulatorView mEmulatorView; + private TermViewFlipper mViewFlipper; /** - * The name of our emulator view in the view resource. + * The name of the ViewFlipper in the resources. */ - private static final int EMULATOR_VIEW = R.id.emulatorView; + private static final int VIEW_FLIPPER = R.id.view_flipper; - private TermSession mTermSession; - - private String mInitialCommand; + private ArrayList mTermSessions; private SharedPreferences mPrefs; private TermSettings mSettings; @@ -74,12 +78,27 @@ public class Term extends Activity { private boolean mAlreadyStarted = false; - public TermService mTermService; private Intent TSIntent; private PowerManager.WakeLock mWakeLock; private WifiManager.WifiLock mWifiLock; + private TermService mTermService; + private ServiceConnection mTSConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName className, IBinder service) { + Log.i(TermDebug.LOG_TAG, "Bound to TermService"); + TermService.TSBinder binder = (TermService.TSBinder) service; + mTermService = binder.getService(); + populateViewFlipper(); + } + + @Override + public void onServiceDisconnected(ComponentName arg0) { + mTermService = null; + } + }; + @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); @@ -90,35 +109,12 @@ public void onCreate(Bundle icicle) { TSIntent = new Intent(this, TermService.class); startService(TSIntent); - setContentView(R.layout.term_activity); - - mEmulatorView = (EmulatorView) findViewById(EMULATOR_VIEW); - - /* Check whether we've received an initial command from the - * launching application - */ - mInitialCommand = mSettings.getInitialCommand(); - String iInitialCommand = getIntent().getStringExtra("jackpal.androidterm.iInitialCommand"); - if (iInitialCommand != null) { - if (mInitialCommand != null) { - mInitialCommand += "\r" + iInitialCommand; - } else { - mInitialCommand = iInitialCommand; - } + if (!bindService(TSIntent, mTSConnection, BIND_AUTO_CREATE)) { + Log.w(TermDebug.LOG_TAG, "bind to service failed!"); } - mTermSession = new TermSession(mSettings, mEmulatorView.getUpdateCallback(), mInitialCommand); - mEmulatorView.initialize(mTermSession); - - DisplayMetrics metrics = new DisplayMetrics(); - getWindowManager().getDefaultDisplay().getMetrics(metrics); - mEmulatorView.setScaledDensity(metrics.scaledDensity); - - mEmulatorView.setFocusable(true); - mEmulatorView.setFocusableInTouchMode(true); - mEmulatorView.requestFocus(); - - registerForContextMenu(mEmulatorView); + setContentView(R.layout.term_activity); + mViewFlipper = (TermViewFlipper) findViewById(VIEW_FLIPPER); PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE); mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TermDebug.LOG_TAG); @@ -129,17 +125,38 @@ public void onCreate(Bundle icicle) { mAlreadyStarted = true; } + private void populateViewFlipper() { + if (mTermService != null) { + mTermSessions = mTermService.getSessions(); + + if (mTermSessions.size() == 0) { + mTermSessions.add(createTermSession()); + } + + for (TermSession session : mTermSessions) { + EmulatorView view = createEmulatorView(session); + mViewFlipper.addView(view); + mViewFlipper.showNext(); + } + + updatePrefs(); + } + } + @Override public void onDestroy() { super.onDestroy(); - mTermSession.finish(); + mViewFlipper.removeAllViews(); + unbindService(mTSConnection); + stopService(TSIntent); + mTermService = null; + mTSConnection = null; if (mWakeLock.isHeld()) { mWakeLock.release(); } if (mWifiLock.isHeld()) { mWifiLock.release(); } - stopService(TSIntent); } private void restart() { @@ -147,10 +164,57 @@ private void restart() { finish(); } + private TermSession createTermSession() { + /* Check whether we've received an initial command from the + * launching application + */ + String initialCommand = mSettings.getInitialCommand(); + String iInitialCommand = getIntent().getStringExtra("jackpal.androidterm.iInitialCommand"); + if (iInitialCommand != null) { + if (initialCommand != null) { + initialCommand += "\r" + iInitialCommand; + } else { + initialCommand = iInitialCommand; + } + } + + return new TermSession(mSettings, null, initialCommand); + } + + private EmulatorView createEmulatorView(TermSession session) { + DisplayMetrics metrics = new DisplayMetrics(); + getWindowManager().getDefaultDisplay().getMetrics(metrics); + EmulatorView emulatorView = new EmulatorView(this, session, mViewFlipper, metrics); + + FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.WRAP_CONTENT, + FrameLayout.LayoutParams.WRAP_CONTENT, + Gravity.LEFT + ); + emulatorView.setLayoutParams(params); + + session.setUpdateCallback(emulatorView.getUpdateCallback()); + + registerForContextMenu(emulatorView); + + return emulatorView; + } + + private TermSession getCurrentTermSession() { + return mTermSessions.get(mViewFlipper.getDisplayedChild()); + } + + private EmulatorView getCurrentEmulatorView() { + return (EmulatorView) mViewFlipper.getCurrentView(); + } + private void updatePrefs() { DisplayMetrics metrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(metrics); - mEmulatorView.updatePrefs(mSettings, metrics); + + for (View v : mViewFlipper) { + ((EmulatorView) v).updatePrefs(mSettings, metrics); + } { Window win = getWindow(); WindowManager.LayoutParams params = win.getAttributes(); @@ -174,20 +238,25 @@ public void onResume() { mPrefs = PreferenceManager.getDefaultSharedPreferences(this); mSettings.readPrefs(mPrefs); updatePrefs(); - mEmulatorView.onResume(); + + mViewFlipper.resumeCurrentView(); } @Override public void onPause() { super.onPause(); - mEmulatorView.onPause(); + + mViewFlipper.pauseCurrentView(); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - mEmulatorView.updateSize(true); + EmulatorView v = (EmulatorView) mViewFlipper.getCurrentView(); + if (v != null) { + v.updateSize(true); + } } @Override @@ -251,7 +320,7 @@ public void onCreateContextMenu(ContextMenu menu, View v, public boolean onContextItemSelected(MenuItem item) { switch (item.getItemId()) { case SELECT_TEXT_ID: - mEmulatorView.toggleSelectingText(); + getCurrentEmulatorView().toggleSelectingText(); return true; case COPY_ALL_ID: doCopyAll(); @@ -289,14 +358,14 @@ private void doEmailTranscript() { new Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:" + addr)); - intent.putExtra("body", mEmulatorView.getTranscriptText().trim()); + intent.putExtra("body", getCurrentTermSession().getTranscriptText().trim()); startActivity(intent); } private void doCopyAll() { ClipboardManager clip = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); - clip.setText(mEmulatorView.getTranscriptText().trim()); + clip.setText(getCurrentTermSession().getTranscriptText().trim()); } private void doPaste() { @@ -310,7 +379,7 @@ private void doPaste() { Log.e(TermDebug.LOG_TAG, "UTF-8 encoding not found."); return; } - mTermSession.write(paste.toString()); + getCurrentTermSession().write(paste.toString()); } private void doDocumentKeys() { diff --git a/src/jackpal/androidterm/TermService.java b/src/jackpal/androidterm/TermService.java index fbf28117e..d98aab267 100644 --- a/src/jackpal/androidterm/TermService.java +++ b/src/jackpal/androidterm/TermService.java @@ -16,7 +16,10 @@ package jackpal.androidterm; +import java.util.ArrayList; + import android.app.Service; +import android.os.Binder; import android.os.IBinder; import android.content.Intent; import android.util.Log; @@ -24,6 +27,7 @@ import android.app.NotificationManager; import android.app.PendingIntent; +import jackpal.androidterm.session.TermSession; import jackpal.androidterm.util.ServiceForegroundCompat; public class TermService extends Service @@ -34,6 +38,16 @@ public class TermService extends Service private static final int RUNNING_NOTIFICATION = 1; private ServiceForegroundCompat compat; + private ArrayList mTermSessions; + + public class TSBinder extends Binder { + TermService getService() { + Log.i("TermService", "Activity binding to service"); + return TermService.this; + } + } + private final IBinder mTSBinder = new TSBinder(); + @Override public void onStart(Intent intent, int flags) { } @@ -45,12 +59,14 @@ public int onStartCommand(Intent intent, int flags, int startId) { @Override public IBinder onBind(Intent intent) { - return null; + Log.i("TermService", "Activity called onBind()"); + return mTSBinder; } @Override public void onCreate() { compat = new ServiceForegroundCompat(this); + mTermSessions = new ArrayList(); /* Put the service in the foreground. */ Notification notification = new Notification(R.drawable.app_terminal, getText(R.string.service_notify_text), System.currentTimeMillis()); @@ -68,6 +84,14 @@ public void onCreate() { @Override public void onDestroy() { compat.stopForeground(true); + for (TermSession session : mTermSessions) { + session.finish(); + } + mTermSessions.clear(); return; } + + public ArrayList getSessions() { + return mTermSessions; + } } diff --git a/src/jackpal/androidterm/TermViewFlipper.java b/src/jackpal/androidterm/TermViewFlipper.java new file mode 100644 index 000000000..3a1b64e53 --- /dev/null +++ b/src/jackpal/androidterm/TermViewFlipper.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2011 Steven Luo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jackpal.androidterm; + +import java.util.Iterator; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.widget.Toast; +import android.widget.ViewFlipper; + +public class TermViewFlipper extends ViewFlipper implements Iterable { + private Context context; + private Toast mToast; + + class ViewFlipperIterator implements Iterator { + int pos = 0; + + @Override + public boolean hasNext() { + return (pos < getChildCount()); + } + + @Override + public View next() { + return getChildAt(pos++); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } + + public TermViewFlipper(Context context) { + super(context); + this.context = context; + } + + public TermViewFlipper(Context context, AttributeSet attrs) { + super(context, attrs); + this.context = context; + } + + @Override + public Iterator iterator() { + return new ViewFlipperIterator(); + } + + public void pauseCurrentView() { + EmulatorView view = (EmulatorView) getCurrentView(); + if (view == null) { + return; + } + view.onPause(); + } + + public void resumeCurrentView() { + EmulatorView view = (EmulatorView) getCurrentView(); + if (view == null) { + return; + } + view.onResume(); + } + + private void showTitle() { + if (getChildCount() == 0) { + return; + } + String title = "Window " + (getDisplayedChild()+1); + if (mToast == null) { + mToast = Toast.makeText(context, title, Toast.LENGTH_SHORT); + } else { + mToast.setText(title); + } + mToast.show(); + } + + @Override + public void showPrevious() { + pauseCurrentView(); + super.showPrevious(); + showTitle(); + resumeCurrentView(); + } + + @Override + public void showNext() { + pauseCurrentView(); + super.showNext(); + showTitle(); + resumeCurrentView(); + } + + @Override + public void setDisplayedChild(int position) { + pauseCurrentView(); + super.setDisplayedChild(position); + showTitle(); + resumeCurrentView(); + } +} diff --git a/src/jackpal/androidterm/session/TermSession.java b/src/jackpal/androidterm/session/TermSession.java index b5d567c05..c529fb18e 100644 --- a/src/jackpal/androidterm/session/TermSession.java +++ b/src/jackpal/androidterm/session/TermSession.java @@ -116,6 +116,10 @@ public void run() { try { while(true) { int read = mTermIn.read(mBuffer); + if (read == -1) { + // EOF -- process exited + return; + } mByteQueue.write(mBuffer, 0, read); mMsgHandler.sendMessage( mMsgHandler.obtainMessage(NEW_INPUT)); @@ -239,6 +243,10 @@ public void updateSize(int columns, int rows) { mEmulator.updateSize(columns, rows); } + public String getTranscriptText() { + return mTranscriptScreen.getTranscriptText(); + } + /** * Look for new input from the ptty, send it to the terminal emulator. */ diff --git a/src/jackpal/androidterm/session/TerminalEmulator.java b/src/jackpal/androidterm/session/TerminalEmulator.java index 6ad27f525..6e60106aa 100644 --- a/src/jackpal/androidterm/session/TerminalEmulator.java +++ b/src/jackpal/androidterm/session/TerminalEmulator.java @@ -1227,9 +1227,6 @@ public void reset() { blockClear(0, 0, mColumns, mRows); } - public String getTranscriptText() { - return mScreen.getTranscriptText(); - } public String getSelectedText(int x1, int y1, int x2, int y2) { return mScreen.getSelectedText(x1, y1, x2, y2); } From 9c11960500eb43050c00883be4d28a68537a4cb7 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Sat, 27 Aug 2011 13:33:30 -0700 Subject: [PATCH 089/847] Add UI for switching and managing terminal sessions. We expose three different ways to manage terminal sessions: * Options menu items to open a new window and close the current window. * A WindowList activity, reachable from the options menu, which lists all the open windows and allows opening, closing, and switching of windows. The behavior is patterned after the window list activity in the Android browser. * Gestures to change windows -- swipe right-to-left for the next window, left-to-right for the previous window. The options menu items have been reordered to make what should be the five most frequently used items (new window, close window, window list, toggle keyboard, special keys reference) appear in the icon menu so that the user doesn't have to touch "More" to reach them. This patch adds several icons. btn_close_window and ic_menu_windows come from the Android browser source, while the rest come from the Android 2.3.3 SDK platform, revision 2 (platforms/android-10/data/res). --- AndroidManifest.xml | 1 + res/drawable-hdpi/btn_close_window.png | Bin 0 -> 1010 bytes res/drawable-hdpi/ic_menu_back.png | Bin 0 -> 1583 bytes res/drawable-hdpi/ic_menu_forward.png | Bin 0 -> 1580 bytes res/drawable-hdpi/ic_menu_windows.png | Bin 0 -> 1550 bytes res/drawable-mdpi/btn_close_window.png | Bin 0 -> 607 bytes res/drawable-mdpi/ic_menu_back.png | Bin 0 -> 1163 bytes res/drawable-mdpi/ic_menu_forward.png | Bin 0 -> 1163 bytes res/drawable-mdpi/ic_menu_windows.png | Bin 0 -> 1234 bytes res/drawable/close_background.xml | 20 +++ res/layout/window_list_item.xml | 47 ++++++ res/layout/window_list_new_window.xml | 27 ++++ res/menu/main.xml | 20 ++- res/values/strings.xml | 5 + src/jackpal/androidterm/EmulatorView.java | 24 ++- src/jackpal/androidterm/Term.java | 81 +++++++++- src/jackpal/androidterm/WindowList.java | 176 ++++++++++++++++++++++ 17 files changed, 391 insertions(+), 10 deletions(-) create mode 100644 res/drawable-hdpi/btn_close_window.png create mode 100644 res/drawable-hdpi/ic_menu_back.png create mode 100644 res/drawable-hdpi/ic_menu_forward.png create mode 100644 res/drawable-hdpi/ic_menu_windows.png create mode 100644 res/drawable-mdpi/btn_close_window.png create mode 100644 res/drawable-mdpi/ic_menu_back.png create mode 100644 res/drawable-mdpi/ic_menu_forward.png create mode 100644 res/drawable-mdpi/ic_menu_windows.png create mode 100644 res/drawable/close_background.xml create mode 100644 res/layout/window_list_item.xml create mode 100644 res/layout/window_list_new_window.xml create mode 100644 src/jackpal/androidterm/WindowList.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 049286d99..eec6bc921 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -18,6 +18,7 @@ + diff --git a/res/drawable-hdpi/btn_close_window.png b/res/drawable-hdpi/btn_close_window.png new file mode 100644 index 0000000000000000000000000000000000000000..12e5c5522d00313bea50820bc76301e6d0557e67 GIT binary patch literal 1010 zcmV0+AQ4r2%o5X0V6>O_u z1C~Vd0KF7c)EDSoQ^W*+P^#}yN=aJ+fk2_;R(uOV1QBn%6G8MskgaJ;ShgnVI-_&K zWZBJ5>e>HBl#iG|Oduu@6Nm}?*BFy18HO?NFZ;`1 zxJ-tA-4NRZx0fW@u@MOyv}z_39ETu1kS;#E0iKP2Gm#{}CffnZw&x|GlK^*qh9pIw zcR&t69DD|xJue9YZ;}n#X6PC9ev|Eingl6{_|?yH=|`f_Hh4OGJ_bQN;0G9(B=1+3)S`eO_2txC8HILC%veLxfF8AW5?`12cb% zgXbhRH#Z-HP61?!0H;V4_yX#1@SFs!#7*=;zLGi#;PQ{-48gW}=LQS}wz0ACgprsa z!RG?L^8>F&tJV4ddRNJpsha=-$#puNU+$1W+vyqT7HLbM6vM#da2)3c=}$yK0Jp)F z?d|Py7!Z8FzP`Q;`tvBOtE-DD+5^HN~FIQGp zmL=dR2wWo{8Qo_sX`u3t_=$@?=5yk9vj$=Fes(|?l|bYq_c;0u(cRYNl^DLZy|cRvz=@hHk^T-KR# zxqQ3bZhs2_@7Gcet(~wPj{`kFZ+uxhb=0l85-m21L&vVEe+MumvS#N3PXX$DtLn|&KU`Vd-=!(bh zVk@o68@iZs=#opz#g+)$2n2$z77e-*Jj&IgNd&lk|DH4o0w?ZrS41HGeIO0;fR1V24VJ_+7khY># zww-c`$|V+wMlssDP#94v>L_Qh*xHCPWsFi|pF&LoBq^zR>^OX>SQ+(ZR&967}hui%by_Jm2mwL%m^eWUxlEz-$3 zSGClp1ucZq$mA&nZ_Q94Vh$aQJKECu?eHz>1$k&hXJM4A7S9qs(_{R z_Jf)bFUxk`*MQj?|8#JU<_s3{g>S3ltsM5%Gu%)^wYj@}7E1KmaXy&y@%*^u?h&1H zxYntafe6W`L)qES^m0na8=60Ez*5C9*?wo2x;O(J(xJ;@Cml0qD7p9 z!e-U8K5QNK`W%)o)D@P=_Y0uQ4p>a|E%1GB+Dwi<;oIecA$sZl_#G`p9i6nWr>bSc zPinu}lrVtbv?#DPCh0bCxI)R^rB&i0?&8~>*U~do(x7u&)6tGIX8K}xk)ohJKd`q?$3C||d>3B3 zxw-&0SC+QO4%YPsc}dGJvQAiMw9(F1u^MvMsPboIc}So^xA|?fb*F9Dg=C;3yW2w` zzw)vOH`aCJ{oCr;l!;qkGkqMqQ{c>I)A9)Yr$bh*y++Gs(o^hsN!ky}Pzf(Xo6GYZ z*^-WR$pjIIx;t1O&r+eB*F7n0z4rw%+uI(iy^R)x^h@_^rw8^0?PkaxO#XuI>a|;f zvbCNI?rlEjIcl5;^L{{>9YhHdv)h*ZUVUsbQ@b-1o@+E)!?}LGv_q(iogb??f+{bx zx>Ufo+q4z|Lvx=l4Li0UD zVx6#5?aPhPAB*kZ_Tsp@!_8ylUZ2HQx>L<#s|$zQwh1GLTPyW5lXo57G6ZXXbD+Sr z*6<%)(=l105q>TPel|)u8Q-cJEMHZ4a)fd5 z9D9LQ*}m8!uh1wV0=UArT&IwQyzC5%2z_Qx^2c6w7Qm^>8gXYWvSKf9cE#tk zt;*$MV5Ggn!j)Y;s{72v3vQ8a54a26!`Z9?sT6D0ZGlzW?Y3d1r=qo%h(M6fOc?A{ zh|d>fl8-M{iI7^>o3d_2o-xS_CAFj^SGGHl-O%7SOgkZ2%L{8|U^B3Ea=v43u9EU@ zuhM|$+7S`z94Jt826q6%8V@0hi-4&TJ~B~3p_DFDtBXiAF0OWeN30I_yycx=awF95 z&OrLS5juIFh3Q}M^2@xY(aR9yiZA3b`PXPgLxs`sI2r+dm_m>Xz!B^K-VTOtcW@4N zbb>p<;9$q?U>7(T+^I2L_a6f}0Ur~^_P1i+$v KJ#HWmrvCxLmdVWk literal 0 HcmV?d00001 diff --git a/res/drawable-hdpi/ic_menu_forward.png b/res/drawable-hdpi/ic_menu_forward.png new file mode 100644 index 0000000000000000000000000000000000000000..6b1804f46ab2f166ace31b04d9676e49d53b9301 GIT binary patch literal 1580 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 literal 0 HcmV?d00001 diff --git a/res/drawable-mdpi/btn_close_window.png b/res/drawable-mdpi/btn_close_window.png new file mode 100644 index 0000000000000000000000000000000000000000..53f878beba0525e0925a9cc70084b29a41a56446 GIT binary patch literal 607 zcmV-l0-*hgP)N?f=oW+!vdB6=Q8IJFg5OtMBwN8Yxou5XZH*FvAZro!`3bU^@hyoE z%++B)NJ@zKIk)))4hQvuAh=1A)D&?U4Pcpp&k=iClH@C66Bv~dgqK-`bx_&rkwix?7mj3MS! zJNSYFTs8u*b-mea_LLP27I6g>1;>Cd3vAg4Kms25zCY9jz`SVkp-VWkEkpP+N8P#+5z(V5 z;Y^7M-)$NZviLNLqBldSodCKLg)ca4Tk-NR8jVJG>S07p=3G6DFei(E4}7;dj2ylP z^g%>N><1mk58Phm4F9bZticAh9K6pQjxLVS-BkESROkP&qg+T5!k2O(!H(@I7ZTWh t;zFX0QzGEcuVkY7WmF0H_X2(d7yuG7r4|#LmWluX002ovPDHLkV1j>y18o2R literal 0 HcmV?d00001 diff --git a/res/drawable-mdpi/ic_menu_back.png b/res/drawable-mdpi/ic_menu_back.png new file mode 100644 index 0000000000000000000000000000000000000000..91fa3e74ac7661023f942cc6421ae1c1e529cf46 GIT binary patch literal 1163 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}a}trX+877Y2q^y~;*F9%q3^WHFHT z0Ash4*>)hovI56+APv^baPD1CI0FN7oTrOpNW|f{(*iT5g-RT+KX3W`cS+usvqe^S zr_5N^cvnOuwnFr%k+M>ZiuG#=Eq?$wyR zl=Fs}@R!^JWyZ;O|8uO_UZ58F{=r*m-}}sa9wa=!)V6NA)|BP0pYK^OeYrb8>*kYt zJ+t2L{Zd((`!+OBHvGR+0`v2XDQVZmY=2kyU;i`fQ;3#g%!x;5R38^zlz(h^ZI)47 zr&+FSb?roLuJAc3eCqL^PX{)oo#0iTa%!#DjAaMR*CcJu4Gj8GB(JNes!_b~;#*8Z62HQ#{K^MdiE@beo=6<6P0kUZV`To+%^wE4`g zHy)L_>uT?RF{k>u&HEgo_JAOhG85kx*JE>cEl{aXsO|s)f6m`+-|qbT_xt~sV+vi;JJ;?@ zkx+j)e=_H-a>>eo(@DiA1pB)~ijS=OT5CS{&gJci9-K+;?Q&~(S(|>WI;+3zdZ4el z_Fu-&Ujv*Ut3R)=F1^quuw?(5>L*L)H!u2C-k)s2skLxo*|JXI1CEwY9t6~8ze=sG z`|!gl!Zfq*(Tqpk-wb5eTsFRF_x*^wcG&$-leJ|fg2htjwkib29i1b=VpnouYf1R^ zXwg&Bmw&u?-WlKcSFFxouHeJf-ev3-yfQO)%Ii1i2Yx#l_u#MORm}wrO#iBXaGUXshs?z)M5sc z(iI#^Kp9DpOmKc$NqJ&XDuZJRP*TA&H!(dmC^a#qvhZZ84Nwt-r>mdKI;Vst0PX?+ A+W-In literal 0 HcmV?d00001 diff --git a/res/drawable-mdpi/ic_menu_forward.png b/res/drawable-mdpi/ic_menu_forward.png new file mode 100644 index 0000000000000000000000000000000000000000..632e4f6db6507f3aee6680180f4015d64bd0dfdc GIT binary patch literal 1163 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}a}trX+877Y2q^y~;*F9%q3^WHFHT z0Ash4*>)hovI56+APv^baPD1CI0FN7oTrOpNW|f{(|j{xT}6)9r$5^_d;0Y0{mU*c z`>3oEqAL5zt(^J7ZhKL?Y+*hf-!1R=fY<7t9~yN7puIt zQr8H{G&E&;x#HM)xl`MGzL(#dW9+?e^2=VvDf=T6rmd3DND`m;j$L?`?9SCQPp%EF zy<6;mGqZfcd2fcKG~UmA7j)n19Is>5NPP4#j8oxxDkY{(ZZd{od{$JMKRIwrRc00sS{yjq@J-jhSwd zv+$wR(%EGu%dd1ky0d5Q&baCakMrkm5Rd6t@Z|lygum|Ahc1SdvAM1>TjC+W*>FQm zJm=@%JKp;JwSlR>M5i_xU#nfx_-d(DeEIHk*Ppx2UH^5VR@ZT5iQ5luuPPS4dHv(^ zuda+cqPR9(nqb)IIirR1lTsp+@}t5E8$Q;_-*V?oycfuO+*J8OoYVux{fz-fLcC|M zm#g`AT0ZOCE9T18BFruavRa=iO$dmJwKD#AH{!F-@uct*R+B}yUvsQ9Kf6k&|54su zdD%rvOW4a9q&dDuC2#kad0CZ~IC0;NxG44uru;EKA1Vhe%T&3nzmxyzmUsJhpIr6I zyG2X;Vc?$d;McR;=T$rZk(3PCA;NsbrlUbav2DW)_D}OKuR3?*SF|qk?{hZUn|^Hx zdZ2UMuSi?`=C|-7HNlm-6Qn!Er1MNxDJ?j6tF5)2uVk+Lw*1{&6@N~Cr{T20B{NvJ zb)D#5F|)Ye86SReOk}yIn)7&l|Ch2q6?e)k63tshUq28M(AgAG@j>tR=IYPe)URl) z;+mzm{d-Bw&ogOv8KM`+9I#)~@cyZq-TspkDCIr)Rf z`Hrz>Gvgcyncpn8&Q#1iKaKZgc4PjltA}=;t+ktXf6Wo)13P>;-`u&vrnPFtBHmZ= zTAW)Csd!D61!gV61CkZe?JQ zT=h^MMMG|WN@iLmZVf`}k&A&E_(3+LW#*Km7Fi|blol}<=oto=q}KqY_>iP>@{>}F z8B9u7a3}#~BtbI4`DrEPiAAXljwwJ%1<%~X^wgl##FWaylc_d9MGT&)hovI56+APv^baPD1CI0FOoQcoAhkch)?r=HJ_2^BeR|NNf$<&*p#PDYcv zx)s)TcdSm*eYL1?u~Te>hTj2dwJ`hl8Gi6rP35J#%>w6B~^Z9GUu-GB~&U@Q6%n?GfcU0xbuq}{(ym!a>se5+y zK49dqJJLB_hJn#VM(<;`TBeb*v#kir0)`dg8#8RLCMhXMoXppAU=VRNIN&Yc8p?3d zG`#!!5B59Ca%DR;{|6NpY!%67Nm7i`He+zSc*UEqfoU4UiXE5!9JzRW-G(`FW_>N* zj1QJG6dRve_PqJC^Yr^!a$H&()2d!FO)zk1j{f)M|dWR{P&>w=ef-uP9OgB*)P1$es{xLu7rk}vu4bgCH00w_5H~`T|U>3+8s$y zxs{dnw)x1`yj2a;k}}=SuFyQlspRy%m1CoKPSVAoM@yL-7_51<#I?6>d|I?MMmkTT zOX$+4+_;myhIcfspSrp+YiZa{j_;A#PSHWLOFBgVOLn?s-00o#`q|5T_HR2K{JJ;} zUf);udu8@w(RcynZ4Xs5RZQOmG(QQD(oy?;{VsdN?=$(rM+I)zKX&SlQhR+RK&p&EYz+l~Cr-QFTcEGF{ty?k(Q!k4Ri z4jAhi&N-Q}^K6?Z+w~QS5mBcuB(9wvxg!5YE}x2$`AJZMV!AxpbHmv)TlT);2Fmw2z<+7I^LdK(PSTAth4wkC4jw6C6? z@mtL+zrSO=miSKb*t{=G)K}hNN?W=2;lAvo)4<%TTH+c}l9E`GYL#4+3Zxi}3=9o) z4J>pGO+t)}t&AN=j;Ro4}mYGwMT4a@! zQ(DAepl29dl3oLp;zN?k$xlixW-uvT!J!0{kp#&E=ckpFCl;kLIHmw46+Ckj(^G>| X6H_V+Po~-c6)||a`njxgN@xNA*9RGP literal 0 HcmV?d00001 diff --git a/res/drawable/close_background.xml b/res/drawable/close_background.xml new file mode 100644 index 000000000..7b266e932 --- /dev/null +++ b/res/drawable/close_background.xml @@ -0,0 +1,20 @@ + + + + + + + diff --git a/res/layout/window_list_item.xml b/res/layout/window_list_item.xml new file mode 100644 index 000000000..70b040fae --- /dev/null +++ b/res/layout/window_list_item.xml @@ -0,0 +1,47 @@ + + + + + + + + diff --git a/res/layout/window_list_new_window.xml b/res/layout/window_list_new_window.xml new file mode 100644 index 000000000..a017d3267 --- /dev/null +++ b/res/layout/window_list_new_window.xml @@ -0,0 +1,27 @@ + + + + diff --git a/res/menu/main.xml b/res/menu/main.xml index a777008fa..fe7d96e66 100644 --- a/res/menu/main.xml +++ b/res/menu/main.xml @@ -16,16 +16,26 @@ --> + + + + + + android:title="@string/preferences" + android:icon="@android:drawable/ic_menu_preferences" /> - - android:title="@string/enable_wakelock" /> diff --git a/res/values/strings.xml b/res/values/strings.xml index a1fb0a763..84eda0284 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -17,6 +17,11 @@ Terminal Emulator Preferences + New window + Close window + Windows + Prev window + Next window Reset term Email to Special keys diff --git a/src/jackpal/androidterm/EmulatorView.java b/src/jackpal/androidterm/EmulatorView.java index e02e52da8..a0beea605 100644 --- a/src/jackpal/androidterm/EmulatorView.java +++ b/src/jackpal/androidterm/EmulatorView.java @@ -66,6 +66,7 @@ public class EmulatorView extends View implements GestureDetector.OnGestureListe private final boolean LOG_KEY_EVENTS = TermDebug.DEBUG && false; private TermSettings mSettings; + private TermViewFlipper mViewFlipper; /** * We defer some initialization until we have been layed out in the view @@ -612,6 +613,8 @@ private void initialize(TermSession session, TermViewFlipper viewFlipper) { mEmulator = session.getEmulator(); mTermOut = session.getTermOut(); + mViewFlipper = viewFlipper; + mKeyListener = new TermKeyListener(); mTextSize = 10; mForeground = TermSettings.WHITE; @@ -621,6 +624,10 @@ private void initialize(TermSession session, TermViewFlipper viewFlipper) { requestFocus(); } + public TermSession getTermSession() { + return mTermSession; + } + /** * Page the terminal view (scroll it up or down by delta screenfulls.) * @@ -713,9 +720,20 @@ public boolean onJumpTapUp(MotionEvent e1, MotionEvent e2) { public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { - // TODO: add animation man's (non animated) fling - mScrollRemainder = 0.0f; - onScroll(e1, e2, 2 * velocityX, -2 * velocityY); + if (Math.abs(velocityX) > Math.abs(velocityY)) { + // Assume user wanted side to side movement + if (velocityX > 0) { + // Left to right swipe -- previous window + mViewFlipper.showPrevious(); + } else { + // Right to left swipe -- next window + mViewFlipper.showNext(); + } + } else { + // TODO: add animation man's (non animated) fling + mScrollRemainder = 0.0f; + onScroll(e1, e2, 2 * velocityX, -2 * velocityY); + } return true; } diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index f912dcaef..68481a457 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -48,6 +48,7 @@ import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; import android.widget.FrameLayout; +import android.widget.Toast; import jackpal.androidterm.session.TermSession; import jackpal.androidterm.util.TermSettings; @@ -80,6 +81,9 @@ public class Term extends Activity { private Intent TSIntent; + public static final int REQUEST_CHOOSE_WINDOW = 1; + public static final String EXTRA_WINDOW_ID = "jackpal.androidterm.window_id"; + private PowerManager.WakeLock mWakeLock; private WifiManager.WifiLock mWifiLock; @@ -136,7 +140,6 @@ private void populateViewFlipper() { for (TermSession session : mTermSessions) { EmulatorView view = createEmulatorView(session); mViewFlipper.addView(view); - mViewFlipper.showNext(); } updatePrefs(); @@ -213,7 +216,8 @@ private void updatePrefs() { getWindowManager().getDefaultDisplay().getMetrics(metrics); for (View v : mViewFlipper) { - ((EmulatorView) v).updatePrefs(mSettings, metrics); + ((EmulatorView) v).setDensity(metrics); + ((EmulatorView) v).updatePrefs(mSettings); } { Window win = getWindow(); @@ -239,6 +243,16 @@ public void onResume() { mSettings.readPrefs(mPrefs); updatePrefs(); + if (mTermSessions != null && mTermSessions.size() < mViewFlipper.getChildCount()) { + for (int i = 0; i < mViewFlipper.getChildCount(); ++i) { + EmulatorView v = (EmulatorView) mViewFlipper.getChildAt(i); + if (!mTermSessions.contains(v.getTermSession())) { + mViewFlipper.removeView(v); + --i; + } + } + } + mViewFlipper.resumeCurrentView(); } @@ -270,6 +284,12 @@ public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); if (id == R.id.menu_preferences) { doPreferences(); + } else if (id == R.id.menu_new_window) { + doCreateNewWindow(); + } else if (id == R.id.menu_close_window) { + doCloseWindow(); + } else if (id == R.id.menu_window_list) { + startActivityForResult(new Intent(this, WindowList.class), REQUEST_CHOOSE_WINDOW); } else if (id == R.id.menu_reset) { doResetTerminal(); } else if (id == R.id.menu_send_email) { @@ -286,6 +306,63 @@ public boolean onOptionsItemSelected(MenuItem item) { return super.onOptionsItemSelected(item); } + private void doCreateNewWindow() { + if (mTermSessions == null) { + Log.w(TermDebug.LOG_TAG, "Couldn't create new window because mTermSessions == null"); + return; + } + + TermSession session = createTermSession(); + mTermSessions.add(session); + + EmulatorView view = createEmulatorView(session); + view.updatePrefs(mSettings); + + mViewFlipper.addView(view); + mViewFlipper.setDisplayedChild(mViewFlipper.getChildCount()-1); + } + + private void doCloseWindow() { + if (mTermSessions == null) { + return; + } + + EmulatorView view = getCurrentEmulatorView(); + if (view == null) { + return; + } + TermSession session = mTermSessions.remove(mViewFlipper.getDisplayedChild()); + view.onPause(); + session.finish(); + mViewFlipper.removeView(view); + if (mTermSessions.size() == 0) { + finish(); + } else { + mViewFlipper.showNext(); + } + } + + @Override + protected void onActivityResult(int request, int result, Intent data) { + switch (request) { + case REQUEST_CHOOSE_WINDOW: + if (result == RESULT_OK && data != null) { + int position = data.getIntExtra(EXTRA_WINDOW_ID, -2); + if (position >= 0) { + mViewFlipper.setDisplayedChild(position); + } else if (position == -1) { + doCreateNewWindow(); + } + } else { + // Close the activity if user closed all sessions + if (mTermSessions.size() == 0) { + finish(); + } + } + break; + } + } + @Override public boolean onPrepareOptionsMenu(Menu menu) { MenuItem wakeLockItem = menu.findItem(R.id.menu_toggle_wakelock); diff --git a/src/jackpal/androidterm/WindowList.java b/src/jackpal/androidterm/WindowList.java new file mode 100644 index 000000000..92780c713 --- /dev/null +++ b/src/jackpal/androidterm/WindowList.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2011 Steven Luo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jackpal.androidterm; + +import java.util.ArrayList; + +import android.app.ListActivity; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.os.IBinder; +import android.util.AttributeSet; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.TextView; + +import jackpal.androidterm.session.TermSession; + +public class WindowList extends ListActivity { + private ArrayList sessions; + private WindowListAdapter mWindowListAdapter; + private TermService mTermService; + + class WindowListAdapter extends BaseAdapter { + private LayoutInflater inflater = getLayoutInflater(); + + @Override + public int getCount() { + return sessions.size(); + } + + @Override + public Object getItem(int position) { + return sessions.get(position); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View child = inflater.inflate(R.layout.window_list_item, parent, false); + TextView label = (TextView) child.findViewById(R.id.window_list_label); + label.setText("Window " + (position+1)); + + View close = child.findViewById(R.id.window_list_close); + final TermService service = mTermService; + final int closePosition = position; + close.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + TermSession session = service.getSessions().remove(closePosition); + if (session != null) { + session.finish(); + notifyDataSetChanged(); + } + } + }); + + return child; + } + } + + /** + * View which isn't automatically in the pressed state if its parent is + * pressed. This allows the window's entry to be pressed without the close + * button being triggered. + * Idea and code shamelessly borrowed from the Android browser's tabs list. + */ + private static class CloseButton extends ImageView { + public CloseButton(Context context) { + super(context); + } + + public CloseButton(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public CloseButton(Context context, AttributeSet attrs, int style) { + super(context, attrs, style); + } + + @Override + public void setPressed(boolean pressed) { + if (pressed && ((View) getParent()).isPressed()) { + return; + } + super.setPressed(pressed); + } + } + + private ServiceConnection mTSConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName className, IBinder service) { + TermService.TSBinder binder = (TermService.TSBinder) service; + mTermService = binder.getService(); + populateList(); + } + + @Override + public void onServiceDisconnected(ComponentName arg0) { + mTermService = null; + } + }; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + setTitle(R.string.window_list); + + ListView listView = getListView(); + View newWindow = getLayoutInflater().inflate(R.layout.window_list_new_window, listView, false); + listView.addHeaderView(newWindow, null, true); + + setResult(RESULT_CANCELED); + } + + @Override + protected void onResume() { + super.onResume(); + + Intent TSIntent = new Intent(this, TermService.class); + if (!bindService(TSIntent, mTSConnection, BIND_AUTO_CREATE)) { + Log.w(TermDebug.LOG_TAG, "bind to service failed!"); + } + } + + @Override + protected void onPause() { + super.onPause(); + + unbindService(mTSConnection); + } + + private void populateList() { + sessions = mTermService.getSessions(); + + if (mWindowListAdapter == null) { + setListAdapter(new WindowListAdapter()); + } else { + mWindowListAdapter.notifyDataSetChanged(); + } + } + + @Override + protected void onListItemClick(ListView l, View v, int position, long id) { + Intent data = new Intent(); + data.putExtra(Term.EXTRA_WINDOW_ID, position-1); + setResult(RESULT_OK, data); + finish(); + } +} From 6b70a47d0f7dedee3257056163c09cb3be631a36 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 27 Aug 2011 21:43:09 -0700 Subject: [PATCH 090/847] Fix resource-related problems when running on Android 1.5 to 2.2 Hurray for the emulator for making it easy to test older versions + Use our own copies of system drawables not available in older versions. + Make sure there's a (mdpi resolution) copy of every drawable in the plain "drawable" directory, since that's where Android 1.5 looks. --- res/drawable-hdpi/ic_menu_add.png | Bin 0 -> 2891 bytes res/drawable-hdpi/ic_menu_close_clear_cancel.png | Bin 0 -> 3698 bytes res/drawable-hdpi/ic_menu_preferences.png | Bin 0 -> 3106 bytes res/drawable-ldpi/ic_menu_add.png | Bin 0 -> 1580 bytes res/drawable-ldpi/ic_menu_close_clear_cancel.png | Bin 0 -> 1851 bytes res/drawable-ldpi/ic_menu_preferences.png | Bin 0 -> 1601 bytes res/drawable-mdpi/ic_menu_add.png | Bin 0 -> 2011 bytes res/drawable-mdpi/ic_menu_close_clear_cancel.png | Bin 0 -> 2425 bytes res/drawable-mdpi/ic_menu_preferences.png | Bin 0 -> 2096 bytes res/drawable/btn_close_window.png | Bin 0 -> 607 bytes res/drawable/ic_menu_add.png | Bin 0 -> 2011 bytes res/drawable/ic_menu_back.png | Bin 0 -> 1163 bytes res/drawable/ic_menu_close_clear_cancel.png | Bin 0 -> 2425 bytes res/drawable/ic_menu_forward.png | Bin 0 -> 1163 bytes res/drawable/ic_menu_preferences.png | Bin 0 -> 2096 bytes res/drawable/ic_menu_windows.png | Bin 0 -> 1234 bytes res/menu/main.xml | 6 +++--- 17 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 res/drawable-hdpi/ic_menu_add.png create mode 100644 res/drawable-hdpi/ic_menu_close_clear_cancel.png create mode 100644 res/drawable-hdpi/ic_menu_preferences.png create mode 100644 res/drawable-ldpi/ic_menu_add.png create mode 100644 res/drawable-ldpi/ic_menu_close_clear_cancel.png create mode 100644 res/drawable-ldpi/ic_menu_preferences.png create mode 100644 res/drawable-mdpi/ic_menu_add.png create mode 100644 res/drawable-mdpi/ic_menu_close_clear_cancel.png create mode 100644 res/drawable-mdpi/ic_menu_preferences.png create mode 100644 res/drawable/btn_close_window.png create mode 100644 res/drawable/ic_menu_add.png create mode 100644 res/drawable/ic_menu_back.png create mode 100644 res/drawable/ic_menu_close_clear_cancel.png create mode 100644 res/drawable/ic_menu_forward.png create mode 100644 res/drawable/ic_menu_preferences.png create mode 100644 res/drawable/ic_menu_windows.png diff --git a/res/drawable-hdpi/ic_menu_add.png b/res/drawable-hdpi/ic_menu_add.png new file mode 100644 index 0000000000000000000000000000000000000000..7b0dfc5e163e8aea196acbf59c06521de57437bc GIT binary patch literal 2891 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 literal 0 HcmV?d00001 diff --git a/res/drawable-ldpi/ic_menu_add.png b/res/drawable-ldpi/ic_menu_add.png new file mode 100644 index 0000000000000000000000000000000000000000..89620af8c03d187d47c7630e9c2a257b88e9e176 GIT binary patch literal 1580 zcmZ{kdpy&77{`CLtrkgXHMbPA5eL65%2o}_Fw6atwOqHEYnaPyO9`by7w0q~x8xea z;^@Xf$wEl!GQ}YwNff1+GyQeWALozf`+j|&_xJPq{`0(^vyS%G3UaD)001aZY%HB+ z)Ltn#RCZF=Ic}C=wI9{m5&+86P&nCbUAPS`QbwC!WhD+Kt~6u9E!Z;t$^x-#)h+-4 zbeUpl;cD|}^m1HC|MM-6%*NjS6y%mT?CcRfC4w8TlGmLfkjfLC3ZWpjYj^TF{|5%} zOwc%>g1{&~YgF1>Y5_Cu4cK8X1nZe8v(6hmj=b`bkDwIXg4xph9t*s42dCeP%pR*A zPLbXmYZ7$d&J#4^#|GpFX`&3STi@z|CrA9#M?f$`*kRQXp4~JW4IOnz9hR3Sf{A6v z#5r)n;--O~w9$jb!iA4R=)iOa#~m5iiBZ|PxemQ}DsOdncq+G%HsOBWC6yDuxAS18 zSOJo|eOES~lZ>Z8!jlXJwWn^TmYlZV`k2Qvy?)J3)bQLr2xcn?=$TUd=zy-n6y<%0 zt$%7e_c@=ok7AHJS!5^O_Q}&Hull#h8b$&8Ixu+F4dRD{pW0fMk?tE?0=Mp2RQY`*jPQ^Q<_u8d%9BErzZ5HO|Kyne+ z?YnWms^gZ8y+xrDNT6DX$%H@ zCgXDQ)~}f=I;GL~LufKaoUL(-zZxEBe)jtM zaORpszV67k^ls~%RZfL*y}b|FH*uMHbUIrV!gnj(IxVn1WEXQu&-Pg+RU=~^-wzzu zZTdSd(TMoRZ^o(*oa*_|*gEl2fm6*Mi9#DgK0MX}^2t#xsC==wIsaQjGDY_&efG?s z09o@!(#v_Tkcst9fR3`F{TX6T{5VBQY*^G+wx%^?j7C?zrNVven0=U*ADFCaqE@JL zzcTuJyyV*u`vPdI-!Q2)KAi2@?fj!uw08Jf-~E)~_*LX~s-WX`42MteO*_8>@ItxN z26ZFvhElGy%oZG=cgGZqXnyx2TFZ(eLbQPHLs2>xqtQ#;ARA|=8G4$c&(RIZnazvM zD$O^=A|cC}x^RBv#hmA6fvbXLRxJ#3!^-^ri}uS$X|?w-*kkR8tXH!6GvvCE_EzF_ zyc_FK$Yot4jhQti_GwD@eB|sFevmt?zJ&;vKbTt8aPm707hW74W`8Wa6vHDNX9P<$ z^-Pf3$|lOGCbXSz%pxA9XJjworDeS1V#5!o1wp=wBQ&bwA?jfv3`Z|_=$fod)$b`q z^*P?D^hd^pWfMbt)_0g5*pgAl&+_Q7SM^}%vDW-_d{*W%k4qEJCg7V_c@>{Mp@3hg z3ya)a=~|Of7DW}|A6OUIUA{WND8}Lu<3#t*Y^&T=JhlXc#llpZEWPAzP)D&8xWbgMVO16pKUf z3yJ0cpdr>JihWF`HvCJ4g!!=|0psXWxR%VQtQeOt|0u36hXq(LWpW24)R)b2X8AIC T5n|Rp*&~2LwzsSz`6T=U5hc46 literal 0 HcmV?d00001 diff --git a/res/drawable-ldpi/ic_menu_close_clear_cancel.png b/res/drawable-ldpi/ic_menu_close_clear_cancel.png new file mode 100644 index 0000000000000000000000000000000000000000..760b9254d7a05fb214c077e959d09c60a8e193b1 GIT binary patch literal 1851 zcmZ{lXE@t?8^`}g)v9rM)M=5b7AML%C2-oLA?0@w~Y2&-MF#?(cQKywmJ#EckdNc>n<5v$8aGU{UeB zxY$`8$!#ah0-G<=!W00iA1Y|EUfiLUuHh_N{$1unL5Am_k)cL77XQuycQ~c80Dyzv z%GAioa$vN8f}h5T4#3CvPPT){gdHR~a3jV&z|~c`sWBd#VMHXBBRO7m`)xka17>Vk7nBKQ-m@*FxqH@@W^kP?Uddv6Q<+8{)b0*H69`Ze7VnA7oJw3&*)=0( zbGSi}`kOz+-Xq3xR@oj~(K^Q7FBc0Q=FHTO3lcjWjG@o`L*hL--OMix5Cbs#CDs!r zIYt7@cjHxK(xg_30wf`0{q6~ClY_?{{X#MYMo>p8)m}^g(R5&_*xs|pXOSVctJF40 zm%@3Ixgk)?yfh!n!Z7}=mR(m5o>eMH$j*4S`^W*=X zVp}$v-{afWy@Nu$6A9jUH|tdsS5XgELNi^rGWgn36)gu4IPzYERHY5IU2D+TLAv8I zIG4YL*w5>FgxIQiNKF)tr5B55h~sJ_ZG@v}3^HS1LwK)YslMRe$BK=YuFH6!`&b|V?#N9Oz*m=r zO&vC(YV31H=zJUAw($1u&f(PP*IhrpmF6Egcn9elddz{rryq{=otzFl)wU!I9{D&3 zoxN>KQRr%znArYHK|$k)vfy9Veha>{y}f@XIlCE07mvE5ivlgHMkZOC-Z$tiwhz2c z|N11)d4b;WVi-zl%ziTUC5D7Q3+seN36-tPKk7|Rlf@Jsd&_b!F}A-R`Fy<@Rj&0i z1YF+C$OUn$9EYt1$puZoc72%|3-9tmXzq&IoGplrCvY57z#7JA`s)-$?Hqursj8<; z2HQ`mTng-DtFFu`vEi=q&CL%lmgJ2+UQRlknohqSz9dQYJeYnpMD@7iYh~1`{OW;s zlpt~4*Sk-*{QC3U(|8WcGHTE*A)5W6{9y`gX2HE)&3+zy}gR1C~^4;z~WD=}@ku~o)d`lBZ9_vE) z{$^W47A={A^YV$kR8{sEx)yKofSo6$M=K*V5xW-d)IGqNIUI{6vZKY*d&;b``;Xfa zz&GzTc8riOs=NIw6B!XSs!qm5&SW0?Gu_8y#J24v}>y*~Ul*a#< zKVMBFzTuuRPPu|u*>(e${#oVq6wkBj0VV<)zYC2$3_=)2AOC>E-_Ab|MHn$pMKW3x z$|W$+w3J^DU-zis5{N%cxtl&YZ-1CTxkn%)khRtp*Q0bBH2Q7`X6SF`^^lQVlQvt^ zge|BYnm(OGQE-No!=<;29O>l#&x<@w&~Rgj_m0N5Pdi8JbG3hNwkFk?aFXvkc5b7S zY9%gqb}PLkNsY(Ahi+gfb(HxD$^rev-9AE33|uk)OPEkGXf1!{_Ba&RvEI~I*{2xH zkKWHuj!lqs$a6rpslglUA3vWuT^@)NR8zlxwwhla_BTw}E3(u$$A#SNo8;{7Pfg&!J|>QXPUJW51)P zAjo3N+#J;xuIy`aiR>-O>({L1JIZCaDA z&04eTCadICL%3%z=jNA^OOUN1k>;uro8nrJ?BkI}SHD|(P+oxr$ccAFfri}4@V}cb zq#cM-M!q{=q|1`UxoMWRVJnIInA4pnSL>FuNW0Gp8_Jn9KRv0#Bp0FF>ft8srt}8TLk+(V! zE`JbWtameno9$3oi5=2U!~*1ik{tQRaN1<$!cV>%fiFtNbbUrxXF`c&h9dc+Nf?-4 z7={HvOB1B21_G&RYB_;GFikK_TSrY(52mS^BUU~8KY?I^e;_vc{|6QkLg$A_;Cur% z5KqE{!F=%%VSuJO$dQDLWhwrDC_KRz6ApkQs<`A?M&jH!60oEwbQlIO@@L5_R>5c- Z#sP!&k0uUb3|K>em6?rcy|K^z{{l+mUl;%Y literal 0 HcmV?d00001 diff --git a/res/drawable-ldpi/ic_menu_preferences.png b/res/drawable-ldpi/ic_menu_preferences.png new file mode 100644 index 0000000000000000000000000000000000000000..efc2f3e4597e6f8aab0ac8e4156e83e999a55d01 GIT binary patch literal 1601 zcmZ{kYdjNr7{`|qRvV2-R;E-g(I(=|Wz=dL*}){!ZORD4u!Y>pNfXmlLyV}9$>pG$ zTckKks$qmN`A|)AH5$v|IbUM0N0!^H&xSsieaK02DQF zXa{#Je=?m&$vL@|A1@gSX`E&HQCR$&xp+sKFmm8lRNw@dBWUTRB*R@pf78oUNSRsta21E zP2E``UHuwA)jzyCn|IhFE)E6WQ#eS|9b%rhdFCINWYN3GZ$<3VFay{H=QV(^$ktlhcC034PYt z&7#B(KKrqM08IASt;lxxo!j=P_@JdMsZeUwYSF_;>_#z0^`-@hKswlTT-=_&EiwcD zA-om+)%_-RS1M%IrbcojuqaT8neULB_Qtd{ZVdTjQgZxYJ3kXym%d#kH{k>ddZ=97 z&l}1jz9Gkn!#FvS$2SuA*L8Fs^GJB3wt2>*l`L7$BVbi)o7@M;Ud&n)$`1DD-(#f%(em`hy4VmY2ehcsEaqebcVXqv& zcCbmqC(>6;9GtcP6Gp122@m935`;LT&X(**uckv}+#-JDx2V;48-qs7)+n`an3bUZUM>Z>7K+th28FTo zA=-GY!4!2r)q6jJo>%!g$r=ZrfrHYq%HO}UIOlkz16ZHgk+dA8*aRriP=w1viJ&k& zh0d0Dt{6~TTBm0NDL}h@@I_}!Jd)`mx%v5~jRnxO%$qQeT?lh(;Upz<2@VK}qI+4Z zq>;26bh6t&J0>)0sfpQvC%hxG=oA6ViCwIiKD-$r$*ER5OeGq|#KjeiIC>SL8@lFn z?c25xg%S87U25h)&kZMZ>s#ewSY@E~*}j2o&N21(Mxk`Or*kDc$hYtAt+aj>`rMxs zcaEN4c`P^U&cz~f>B2r+lw&48cCVF@Rd7@H9HX9Uj>sY0`vBTZ%c(8t+?m9wo#o}d z06U)eFb)PoGDsd)X%Po8`|?Ij1Abn6ek$5<-E^~ShGmWER*tt0Yrd0Qx7BoV9UJPb zkh@Cl& z3_C*B&avv+248;)jU0wNPoaka;HC%y%`Zl-!2eQ|kn`klfH}Qbd6(R1uNgv!FD=45 jj0|w_kxLjZ$lH(XM)vlJ3T-3X$cF&9qj+@rVN$|Bz|ZR- literal 0 HcmV?d00001 diff --git a/res/drawable-mdpi/ic_menu_add.png b/res/drawable-mdpi/ic_menu_add.png new file mode 100644 index 0000000000000000000000000000000000000000..57a4099bbb199afe92a9fb3feefc4125190c27c6 GIT binary patch literal 2011 zcmZ{lX*|?x8^`}MVa77d#9&4-wk)B)nbBx+tTXnVp>iz4OdNZbN=ieBl&H{R=d{p* zEQ!O2I)(tlHj*!XZ5zh0)J_@pHxqR(@ffmHCGRul zS=WjhC?l%ttFx||RjANZbn6aMT`1a*29i=PmFD!DvxHK+>?397C6=2{^^S+EwEsom z&N%K{>v`rFWingWw^6Y2ZWCI>x#tr!>-DMBN1@&EV)(~NTC=4_zc7x1knJ>z!6k2( zOU|2050~Tz8w}x6Dh6raHS%9|7j;~im^{fXS=HnKkrk0n+tXRj|Ev^bE{0*aXWuF- zxST%$v_%nTOvru~J#BGD;yJFtmmu+(z3+F`#q)Zbu!)Cbu6XHRy0zzazLCwJa^7t>h|hExD~@C9mwwy68AGXo*@Vgsqj7mYT0U zJd&+~1n6{n!CjAhlY8eEKSp$pQTsZD@)et4QUlny*0n{A_B$=Cxhouu{Pc=gApb$L0tUvbm#^razTeRRu zIvtDun5*3{m-?8Ow-d3@0m?Y7Ebon}+8ka-!nvPY?;g*Z*)+VEc9<3r@`;PK(w-j5 z0uf>^*uF`NwBR#UItLhKnwxV_R0e|(yWp_{4!z~QKu!;$YP+7QQysb~bpy*)jS$J! zyFPEfp%-9OoG{#Q6MmF3?sBY%HxQks()S}o9txfIKTR_68te_x%uztX_VY(dn<R5(Vu0D)AX%E_*~O_Kr3CSNpdRYE+w`!mCB8=bM^-AI*wH1VR$4Go{qMH?0Dk+5pWxj};;VFDG66)O%ypMDg} z8W0;keY5c_Q@{SnyzEIP8)s;3eYMvEm>hpg^s*>MTKMd1$DJ%!cyeen>~>+L6nq6w zdwJ%s26(30XxRxlUVrLz^2(_2dgn!UgwqJPm)9?ni64XBOY`6C%)PGwb0{;zr1PH^ zZ>Za5_|xGsMss)ALOfETkCT=SCxR0+Aw&jS7HhjCWKh(x>%Jmq6`AgE*=|JbRO{j# zK)CRf#w;!|oi}bIB{=;Ickx(#0+oOel&fR67iWSVOuYk?NXm|k))Ax~!TV+&RJm=v zW@~#TJbM%!u|ELr-CvWobf!y5)zdZ_)m^~g!?)S$!JO8E#F2}xxq3kc5Kn_WnFFRe z@}5*aL`ho@OsNb%y_!&$xv(cRQj=e4m{_ahF>#tPfesJnX%Bi1C7Giu(*gP9tnCjN zq{7V?a|F+^$MNeXH_1Zato`FQH@u79LScRFTEWfg`UB2@@+$~zHu~-u9+v&IMQbC_ zdrSSnZl41dJyOfKLBvAWpTsS0H8sBQxA-uP&TJ?ITfWgphvVM+E!j{12$|V$JWjD= zmRG)kzxhBINxb(h`SzQ|cXmCt-l%m{`UNEXl8iO(g-+>N+Pn32+9{p=My)PmTBHv- zDBzHu&;CWwr|eXft50ji^mhHrf&{E2RMr$_)^3&HG?^hi>>bS~A4`oJV8JYFZ!8aQ z{1)0CFjSd)xMY{FN#nE1g9iHA^-Q5H7adH#gp@sxJcsNr?q2g)--eS)^*c`hA@1Iv zLqVlnrCT+vKjwI<*S)$3W5mQ^Z9f10L2gi&mSGB1@9#|+&8Q&7sdd}A)44;OmHUcl zFtyeB7H@)jWyUx~sBwYdjH$;^kHj$O zF-&qm6jJ~I59))uprNil(H&196AqC#0gWyZV*UJA1ghep^I3D-^1GUbsa)N-= zuMNll2#sMzk^MtsqX1A(-z_HatUyWrmkN#WXGR0W*!yr5fx-MTZV|_0PSK;7fEhy| eaW-M}K&C5`&WJm~VHydB02`_!<*~V6(tiLM5`eJ) literal 0 HcmV?d00001 diff --git a/res/drawable-mdpi/ic_menu_close_clear_cancel.png b/res/drawable-mdpi/ic_menu_close_clear_cancel.png new file mode 100644 index 0000000000000000000000000000000000000000..04a76b5890be2084e9626754d949954e5a8ad011 GIT binary patch literal 2425 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 literal 0 HcmV?d00001 diff --git a/res/drawable/ic_menu_add.png b/res/drawable/ic_menu_add.png new file mode 100644 index 0000000000000000000000000000000000000000..57a4099bbb199afe92a9fb3feefc4125190c27c6 GIT binary patch literal 2011 zcmZ{lX*|?x8^`}MVa77d#9&4-wk)B)nbBx+tTXnVp>iz4OdNZbN=ieBl&H{R=d{p* zEQ!O2I)(tlHj*!XZ5zh0)J_@pHxqR(@ffmHCGRul zS=WjhC?l%ttFx||RjANZbn6aMT`1a*29i=PmFD!DvxHK+>?397C6=2{^^S+EwEsom z&N%K{>v`rFWingWw^6Y2ZWCI>x#tr!>-DMBN1@&EV)(~NTC=4_zc7x1knJ>z!6k2( zOU|2050~Tz8w}x6Dh6raHS%9|7j;~im^{fXS=HnKkrk0n+tXRj|Ev^bE{0*aXWuF- zxST%$v_%nTOvru~J#BGD;yJFtmmu+(z3+F`#q)Zbu!)Cbu6XHRy0zzazLCwJa^7t>h|hExD~@C9mwwy68AGXo*@Vgsqj7mYT0U zJd&+~1n6{n!CjAhlY8eEKSp$pQTsZD@)et4QUlny*0n{A_B$=Cxhouu{Pc=gApb$L0tUvbm#^razTeRRu zIvtDun5*3{m-?8Ow-d3@0m?Y7Ebon}+8ka-!nvPY?;g*Z*)+VEc9<3r@`;PK(w-j5 z0uf>^*uF`NwBR#UItLhKnwxV_R0e|(yWp_{4!z~QKu!;$YP+7QQysb~bpy*)jS$J! zyFPEfp%-9OoG{#Q6MmF3?sBY%HxQks()S}o9txfIKTR_68te_x%uztX_VY(dn<R5(Vu0D)AX%E_*~O_Kr3CSNpdRYE+w`!mCB8=bM^-AI*wH1VR$4Go{qMH?0Dk+5pWxj};;VFDG66)O%ypMDg} z8W0;keY5c_Q@{SnyzEIP8)s;3eYMvEm>hpg^s*>MTKMd1$DJ%!cyeen>~>+L6nq6w zdwJ%s26(30XxRxlUVrLz^2(_2dgn!UgwqJPm)9?ni64XBOY`6C%)PGwb0{;zr1PH^ zZ>Za5_|xGsMss)ALOfETkCT=SCxR0+Aw&jS7HhjCWKh(x>%Jmq6`AgE*=|JbRO{j# zK)CRf#w;!|oi}bIB{=;Ickx(#0+oOel&fR67iWSVOuYk?NXm|k))Ax~!TV+&RJm=v zW@~#TJbM%!u|ELr-CvWobf!y5)zdZ_)m^~g!?)S$!JO8E#F2}xxq3kc5Kn_WnFFRe z@}5*aL`ho@OsNb%y_!&$xv(cRQj=e4m{_ahF>#tPfesJnX%Bi1C7Giu(*gP9tnCjN zq{7V?a|F+^$MNeXH_1Zato`FQH@u79LScRFTEWfg`UB2@@+$~zHu~-u9+v&IMQbC_ zdrSSnZl41dJyOfKLBvAWpTsS0H8sBQxA-uP&TJ?ITfWgphvVM+E!j{12$|V$JWjD= zmRG)kzxhBINxb(h`SzQ|cXmCt-l%m{`UNEXl8iO(g-+>N+Pn32+9{p=My)PmTBHv- zDBzHu&;CWwr|eXft50ji^mhHrf&{E2RMr$_)^3&HG?^hi>>bS~A4`oJV8JYFZ!8aQ z{1)0CFjSd)xMY{FN#nE1g9iHA^-Q5H7adH#gp@sxJcsNr?q2g)--eS)^*c`hA@1Iv zLqVlnrCT+vKjwI<*S)$3W5mQ^Z9f10L2gi&mSGB1@9#|+&8Q&7sdd}A)44;OmHUcl zFtyeB7H@)jWyUx~sBwYdjH$;^kHj$O zF-&qm6jJ~I59))uprNil(H&196AqC#0gWyZV*UJA1ghep^I3D-^1GUbsa)N-= zuMNll2#sMzk^MtsqX1A(-z_HatUyWrmkN#WXGR0W*!yr5fx-MTZV|_0PSK;7fEhy| eaW-M}K&C5`&WJm~VHydB02`_!<*~V6(tiLM5`eJ) literal 0 HcmV?d00001 diff --git a/res/drawable/ic_menu_back.png b/res/drawable/ic_menu_back.png new file mode 100644 index 0000000000000000000000000000000000000000..91fa3e74ac7661023f942cc6421ae1c1e529cf46 GIT binary patch literal 1163 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}a}trX+877Y2q^y~;*F9%q3^WHFHT z0Ash4*>)hovI56+APv^baPD1CI0FN7oTrOpNW|f{(*iT5g-RT+KX3W`cS+usvqe^S zr_5N^cvnOuwnFr%k+M>ZiuG#=Eq?$wyR zl=Fs}@R!^JWyZ;O|8uO_UZ58F{=r*m-}}sa9wa=!)V6NA)|BP0pYK^OeYrb8>*kYt zJ+t2L{Zd((`!+OBHvGR+0`v2XDQVZmY=2kyU;i`fQ;3#g%!x;5R38^zlz(h^ZI)47 zr&+FSb?roLuJAc3eCqL^PX{)oo#0iTa%!#DjAaMR*CcJu4Gj8GB(JNes!_b~;#*8Z62HQ#{K^MdiE@beo=6<6P0kUZV`To+%^wE4`g zHy)L_>uT?RF{k>u&HEgo_JAOhG85kx*JE>cEl{aXsO|s)f6m`+-|qbT_xt~sV+vi;JJ;?@ zkx+j)e=_H-a>>eo(@DiA1pB)~ijS=OT5CS{&gJci9-K+;?Q&~(S(|>WI;+3zdZ4el z_Fu-&Ujv*Ut3R)=F1^quuw?(5>L*L)H!u2C-k)s2skLxo*|JXI1CEwY9t6~8ze=sG z`|!gl!Zfq*(Tqpk-wb5eTsFRF_x*^wcG&$-leJ|fg2htjwkib29i1b=VpnouYf1R^ zXwg&Bmw&u?-WlKcSFFxouHeJf-ev3-yfQO)%Ii1i2Yx#l_u#MORm}wrO#iBXaGUXshs?z)M5sc z(iI#^Kp9DpOmKc$NqJ&XDuZJRP*TA&H!(dmC^a#qvhZZ84Nwt-r>mdKI;Vst0PX?+ A+W-In literal 0 HcmV?d00001 diff --git a/res/drawable/ic_menu_close_clear_cancel.png b/res/drawable/ic_menu_close_clear_cancel.png new file mode 100644 index 0000000000000000000000000000000000000000..04a76b5890be2084e9626754d949954e5a8ad011 GIT binary patch literal 2425 zcmZ{mc|6qX9>;%XEMrgBvJ8WWFi03Oc+tro$O0Qha;3|5VB-D zWGQ!yEFG04%V3o3YdLXnr}Njnf80Ml&-?ZLzMs$Q`S(e>WQP+LI4J-CfH2+?>%bxW zS0LP+^FUBnhyxJO8ixgdiZo?C&cq*Lc_o}f%YS0Q^hf<#MucE|IsB&!46dcR006`W zkHt8-zFB>AE9B!RNk+%QBnwqX4Tu<5r4I6aH6#^k1*Ug736Ap>C zR_Iilbd-mRj&HjP0{sS~V}|xL=c6ehG65{*2tK6u_WI8V+(Wv6t3+d(unXMA+YetUZ`9~A398EJCR?h#q zl#v%DEJ-e@I!Os~?`l@cSA};g0u=)BAnwd8xP4jLvHON;EfJ&o1Cn2d5aN8=ruS%3 z0`=5(Cy}nfF7UoePYD3Ik%O6%gA}X0wF2U^8Kw#8#F+10<87W&bLA=E3j3s%9j;g% z36?=KJ!2UGV9c3H&=}o?V)1*ef6l+h8i}~zlO*HV3Xvl68#(Q)=+f#xZ#a3{l;zNS zc%wadYgSwyE}W7BDdyTD^cwjpZ?%VhIbj7_`U#(;cueesXMbk8>7l!s;o{~+)iDRS z6IUx8bx>gM)^VQ&o3&RYx+ECT5b;$IbWST@KYiar+`SMrF)B#}y!S;vAW2gri{uYH z30X(mEppq7#ZG0VS_3ZeXr=CgS{-Dx&4C$j9Ali;!xjQkS_0goDE~&0#c1fv8b52z z48(qlXpfSYpdZj(TTK(nziPH8_5RTCE$?S*cxpHsH}xybn)Z=wq%QpZk-CA+BRPPybSI0Q1+jH zGWlb#;O2yX;danhe1d7X{Nhs8NrzxnAXfRi!kJk$A58qwD#{)rGa|FD$=o`H$QfzV zJsRmAymokzbvS6-dERTyC5uIUd*_9=p7Lig=b`ksM*alF8Mf_X)pxRwHpGXo@rE{R zBFBhc60jG?q&6!4ASHcbf`eA2BM+#J#q%YB&_GRfW_@*sS4?M>9yO`mJu0X89e6I~ z^Bna$JL+QI2(V{@boh2JxlZGOtjX>Oxj@xddfnJWZ?-O5`L#W|vDxBkP4G9*{iXVc zrO&6cWX!P`4b-4UjL@#1?U1QaQXFV9gMe2gK3RVp_?Y*Ce#ZBr-&%%3v1j;H)pO0| z)WW@vlIl_6*G~>MF5QxXfvknr__%kr3xO0xKwO1_{|Fmz=uIyrm(MO=6p@EsKPDAA zP7<3%zj?noT11~EpRi5|sAE30t^j<&7?oQf8<+4%T)eqj+&Kxh4siLK$Ps^<>K3wbc4I*`)f3sTDG7(NGdMNi!-&CcTCsBL*vg2Tr>5vU@p;ojb5|_(w;uQ8 z$D;Gq58S4!QfzTMg3x$lFr||vo+S$f~8*<@_(%ZfV*hRNCdLD^ZY8<+p zc+;rPux^x4JHkwo&LyW(?MGquePWlDXnM-js1q?1_lj(}Tysdr8{1rGZkT65QBxi+ zuiX9qN8{Jn=hyD;C$O#}m6}<)Np=jwXF+|r_GJB{q6Jza+WfbRJV&n~N})!De8 zYH6*HoSbI!DVrHpMen_FO`vHYBjXU_?JsGP z%EvQJ1V}d@{t+!G##0e*(~9`+(V(#KEmmlo!3xkhu2cda?oZn<@Y9jw#l(AUR+=@( zLzf&HCQ-dpSK&_|xt^kvMC3L+_JbJkXUhdn01!2d3>dWwnivjZ0s^@*(0!Zr3AY4= zb0k>d;JU0B0aSd>yRK}q{yTnqgGY3b=Sq(^lFrjMa}V#Ll9HueYj%37j1)P2*%i@N zf9KuMaUK4b)7gpXGIx6F8j7TSXsuqT&~`iWKzm120fSleJ}WvYPF|g%zhl^XnnE01 zeEQ5<1bEr}(JJs;KrPfk=zZeP>hlFjFA*9-z*2O<3YbdLX*xu&kI1Wc$P{bVxDM=E z4P<_GapP0D8UgUczX-XDAaKe2HR~)zQe9Gr{l@lWuOXZB*K-8JlO^(|1o{~oj-)0L zRBg+b3K7*OYa|s~7RX9*^@n3+fyL|r^}{Ks8>wPNp#R9KO`@EACZH3c&sa;A^PslQ z31yk}?T|2F0PDnjgYh|ke&euijHAn};v4}~r=x3v?@w*6#)k|ABhx)(*0HWfSzhpZ zzQx|#T3f%LHi_fdynD3ftuVlArRQ8U*HGow2>$u=NQuWfHr)|ki@XZYpm>wacE2ra z6-kQ8sU?@uLw}%Ca4d0OOh3{MxW4kr_t{S5Eyh0R^-zK1-^^Z9o&n&}HCTiydFDsT z+ZG@GS8^v39Rv*t_Q}U@pG! zuBh4USkY^gUwvGnvjzM(pA1!sxhutoKp`Qng^@S_ z^x(R1Ex3`E?pY^2eT2R-0)hovI56+APv^baPD1CI0FN7oTrOpNW|f{(|j{xT}6)9r$5^_d;0Y0{mU*c z`>3oEqAL5zt(^J7ZhKL?Y+*hf-!1R=fY<7t9~yN7puIt zQr8H{G&E&;x#HM)xl`MGzL(#dW9+?e^2=VvDf=T6rmd3DND`m;j$L?`?9SCQPp%EF zy<6;mGqZfcd2fcKG~UmA7j)n19Is>5NPP4#j8oxxDkY{(ZZd{od{$JMKRIwrRc00sS{yjq@J-jhSwd zv+$wR(%EGu%dd1ky0d5Q&baCakMrkm5Rd6t@Z|lygum|Ahc1SdvAM1>TjC+W*>FQm zJm=@%JKp;JwSlR>M5i_xU#nfx_-d(DeEIHk*Ppx2UH^5VR@ZT5iQ5luuPPS4dHv(^ zuda+cqPR9(nqb)IIirR1lTsp+@}t5E8$Q;_-*V?oycfuO+*J8OoYVux{fz-fLcC|M zm#g`AT0ZOCE9T18BFruavRa=iO$dmJwKD#AH{!F-@uct*R+B}yUvsQ9Kf6k&|54su zdD%rvOW4a9q&dDuC2#kad0CZ~IC0;NxG44uru;EKA1Vhe%T&3nzmxyzmUsJhpIr6I zyG2X;Vc?$d;McR;=T$rZk(3PCA;NsbrlUbav2DW)_D}OKuR3?*SF|qk?{hZUn|^Hx zdZ2UMuSi?`=C|-7HNlm-6Qn!Er1MNxDJ?j6tF5)2uVk+Lw*1{&6@N~Cr{T20B{NvJ zb)D#5F|)Ye86SReOk}yIn)7&l|Ch2q6?e)k63tshUq28M(AgAG@j>tR=IYPe)URl) z;+mzm{d-Bw&ogOv8KM`+9I#)~@cyZq-TspkDCIr)Rf z`Hrz>Gvgcyncpn8&Q#1iKaKZgc4PjltA}=;t+ktXf6Wo)13P>;-`u&vrnPFtBHmZ= zTAW)Csd!D61!gV61CkZe?JQ zT=h^MMMG|WN@iLmZVf`}k&A&E_(3+LW#*Km7Fi|blol}<=oto=q}KqY_>iP>@{>}F z8B9u7a3}#~BtbI4`DrEPiAAXljwwJ%1<%~X^wgl##FWaylc_d9MGT&;%V8Izq9xn?rR5)BCr!x&4Wa5M-9F~b=9GRAi7H1=(XtYIu!vs4ty zek9vb*P4b9anM4IZE~mc*S&w-KR(a<_5Hq|&+GZ~NwzS*&IgtN0|3Bhf-%Ih2>%rj z2kT^>R^?@Z9e?w>Apq2+!_-(4k3YsPkVVWtF&_5P{k07C*C(?0PZt>9NwWa}?o%d) z`c}66+xNq>YzX2*-4nYH0;ASL-a(odRP%C2QbKDBWJ|!K;A90sTa}iP*91z|08|7d zTg>*Sbez7Z5a#v(C{I5hQv~Nu=_^O`@!nIFr8=O*=9t@`gX!xVnT|;#KX2FS9l{faD>F)-yOT1tr!Klb|uoRdZ?>}@-leir~JTHsXX zpD$(8Qer$#!Z<2!#ouymwu_Z+_v_XODG1(K-aJg<1YKlAKl#i!;ZV$Sp*EgNFVqpe z7>#dp=JgYzB+n~Q!(ov$U;ZJdUdX$#%WwsUnb~zs#KW;kf?E90rrWY> zEnT=1{`mNSItMZAG@g1a_LP{tVc4Fr8PKy%BHqmF(*9QMlNfXBiSY8dxWmU3$vwq zaRJRwjFh79pId^)27f_W@C58t{l)A*;Rj?tv}0r5OxM;DYLgT}(8|A(aBfq-CKJ{Bb^S^VgT zkjtaDz!kNm{W2h3bv1Z2h>4GwO(8RP#goo}IKZcx*8QI~f|isph9y6EQBXl4*B=vp z&B*>L9G^J%e44Ot#rDwg-ObEuTv2hXb(}&$xSh)#=^k|<{_}}Y3wcqoxtu`x1zAzX zK-Thct7}ae!(iSY!Rcjn2F%vG9XHKU#(?G;RA_`NHoKc1+nPDb_ctg$YZjG2L}{vV zde{ni&T}r1>)F9e-ls~0)%$IQ_AU!dL4ut`tiz?6n%UK^G6hBdrbvL2mmB47NU^UwiK! z^5DcM&;o65acIA8?ou?EyQdZ1yv%&w^e`6I!l-bnmj(J05~TcQVC6UxAZX=a{Ba>Y zvjDPsa+VF|pJV3^`I*ou!lrFTxrp*S=%2~w@w5Lr@Oj_NDl5!#o)Hi-*ZpARnnAh2WW ztJ{`I%LM_>suMXz$g%o4bZ)Ar49zYlp0meu;9UE`1%1-X^N$`}^|)3_rQDx^(xo49NbYwy9=M9h{t7;8#SZG+HMo75~~5Mi)>WPDY(moY*b?TDMA^shZjqy@3#hj@3M%gX?Pp|F-fj zOuq3BYP!!r?DhA(GM|kD32u!#arwG$YPw$&b}z{-5#hx&nbsOh{ru%Gf;KkMRn}T` z@Nr#(1S)73of?05^{D%4G@Hn*I!wbjgU)hovI56+APv^baPD1CI0FOoQcoAhkch)?r=HJ_2^BeR|NNf$<&*p#PDYcv zx)s)TcdSm*eYL1?u~Te>hTj2dwJ`hl8Gi6rP35J#%>w6B~^Z9GUu-GB~&U@Q6%n?GfcU0xbuq}{(ym!a>se5+y zK49dqJJLB_hJn#VM(<;`TBeb*v#kir0)`dg8#8RLCMhXMoXppAU=VRNIN&Yc8p?3d zG`#!!5B59Ca%DR;{|6NpY!%67Nm7i`He+zSc*UEqfoU4UiXE5!9JzRW-G(`FW_>N* zj1QJG6dRve_PqJC^Yr^!a$H&()2d!FO)zk1j{f)M|dWR{P&>w=ef-uP9OgB*)P1$es{xLu7rk}vu4bgCH00w_5H~`T|U>3+8s$y zxs{dnw)x1`yj2a;k}}=SuFyQlspRy%m1CoKPSVAoM@yL-7_51<#I?6>d|I?MMmkTT zOX$+4+_;myhIcfspSrp+YiZa{j_;A#PSHWLOFBgVOLn?s-00o#`q|5T_HR2K{JJ;} zUf);udu8@w(RcynZ4Xs5RZQOmG(QQD(oy?;{VsdN?=$(rM+I)zKX&SlQhR+RK&p&EYz+l~Cr-QFTcEGF{ty?k(Q!k4Ri z4jAhi&N-Q}^K6?Z+w~QS5mBcuB(9wvxg!5YE}x2$`AJZMV!AxpbHmv)TlT);2Fmw2z<+7I^LdK(PSTAth4wkC4jw6C6? z@mtL+zrSO=miSKb*t{=G)K}hNN?W=2;lAvo)4<%TTH+c}l9E`GYL#4+3Zxi}3=9o) z4J>pGO+t)}t&AN=j;Ro4}mYGwMT4a@! zQ(DAepl29dl3oLp;zN?k$xlixW-uvT!J!0{kp#&E=ckpFCl;kLIHmw46+Ckj(^G>| X6H_V+Po~-c6)||a`njxgN@xNA*9RGP literal 0 HcmV?d00001 diff --git a/res/menu/main.xml b/res/menu/main.xml index fe7d96e66..5c58dd766 100644 --- a/res/menu/main.xml +++ b/res/menu/main.xml @@ -18,10 +18,10 @@ + android:icon="@drawable/ic_menu_add" /> + android:icon="@drawable/ic_menu_close_clear_cancel" /> @@ -31,7 +31,7 @@ android:title="@string/special_keys" /> + android:icon="@drawable/ic_menu_preferences" /> Date: Sun, 28 Aug 2011 09:43:09 -0700 Subject: [PATCH 091/847] Avoid accessing a finished TranscriptScreen when resuming the terminal activity updatePrefs() may ask each terminal session in the list to update its size, which requires accessing the TranscriptScreen. Therefore, we need to update the list of running terminal sessions before calling updatePrefs(), to preclude the possibility of accessing a finished TranscriptScreen. Also, if a window was closed in the WindowList activity, and then the user selected a specific window, the window number passed to us in onActivityResult() refers to the window list *after* taking window closures into account. We therefore need to defer switching to the user's selected window until after the list of running sessions is updated (onActivityResult() is called before onResume(), so it can't be done there); this avoids calling onResume() on a stale EmulatorView, which results in an attempt to access a finished TranscriptScreen. --- src/jackpal/androidterm/Term.java | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index 68481a457..9c9c84ccb 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -83,6 +83,7 @@ public class Term extends Activity { public static final int REQUEST_CHOOSE_WINDOW = 1; public static final String EXTRA_WINDOW_ID = "jackpal.androidterm.window_id"; + private int onResumeSelectWindow = -1; private PowerManager.WakeLock mWakeLock; private WifiManager.WifiLock mWifiLock; @@ -239,21 +240,28 @@ private void updatePrefs() { @Override public void onResume() { super.onResume(); - mPrefs = PreferenceManager.getDefaultSharedPreferences(this); - mSettings.readPrefs(mPrefs); - updatePrefs(); if (mTermSessions != null && mTermSessions.size() < mViewFlipper.getChildCount()) { for (int i = 0; i < mViewFlipper.getChildCount(); ++i) { EmulatorView v = (EmulatorView) mViewFlipper.getChildAt(i); if (!mTermSessions.contains(v.getTermSession())) { + v.onPause(); mViewFlipper.removeView(v); --i; } } } - mViewFlipper.resumeCurrentView(); + mPrefs = PreferenceManager.getDefaultSharedPreferences(this); + mSettings.readPrefs(mPrefs); + updatePrefs(); + + if (onResumeSelectWindow >= 0) { + mViewFlipper.setDisplayedChild(onResumeSelectWindow); + onResumeSelectWindow = -1; + } else { + mViewFlipper.resumeCurrentView(); + } } @Override @@ -349,7 +357,8 @@ protected void onActivityResult(int request, int result, Intent data) { if (result == RESULT_OK && data != null) { int position = data.getIntExtra(EXTRA_WINDOW_ID, -2); if (position >= 0) { - mViewFlipper.setDisplayedChild(position); + // Switch windows after session list is in sync, not here + onResumeSelectWindow = position; } else if (position == -1) { doCreateNewWindow(); } From f727e5f69edd9a3ac66fcc06c6a6abad681195c1 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sun, 28 Aug 2011 10:29:52 -0700 Subject: [PATCH 092/847] Remove JDK 1.5-incompatible interface method @Override annotations Allows building with JDK 1.5. --- src/jackpal/androidterm/EmulatorView.java | 1 - src/jackpal/androidterm/Term.java | 2 -- src/jackpal/androidterm/TermViewFlipper.java | 4 ---- src/jackpal/androidterm/WindowList.java | 7 ------- 4 files changed, 14 deletions(-) diff --git a/src/jackpal/androidterm/EmulatorView.java b/src/jackpal/androidterm/EmulatorView.java index a0beea605..25fba9a13 100644 --- a/src/jackpal/androidterm/EmulatorView.java +++ b/src/jackpal/androidterm/EmulatorView.java @@ -218,7 +218,6 @@ public void run() { * Called by the TermSession when the contents of the view need updating */ private UpdateCallback mUpdateNotify = new UpdateCallback() { - @Override public void onUpdate() { if ( mIsSelectingText ) { int rowShift = mEmulator.getScrollCounter(); diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index 9c9c84ccb..edea81bfa 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -90,7 +90,6 @@ public class Term extends Activity { private TermService mTermService; private ServiceConnection mTSConnection = new ServiceConnection() { - @Override public void onServiceConnected(ComponentName className, IBinder service) { Log.i(TermDebug.LOG_TAG, "Bound to TermService"); TermService.TSBinder binder = (TermService.TSBinder) service; @@ -98,7 +97,6 @@ public void onServiceConnected(ComponentName className, IBinder service) { populateViewFlipper(); } - @Override public void onServiceDisconnected(ComponentName arg0) { mTermService = null; } diff --git a/src/jackpal/androidterm/TermViewFlipper.java b/src/jackpal/androidterm/TermViewFlipper.java index 3a1b64e53..1a1925740 100644 --- a/src/jackpal/androidterm/TermViewFlipper.java +++ b/src/jackpal/androidterm/TermViewFlipper.java @@ -31,17 +31,14 @@ public class TermViewFlipper extends ViewFlipper implements Iterable { class ViewFlipperIterator implements Iterator { int pos = 0; - @Override public boolean hasNext() { return (pos < getChildCount()); } - @Override public View next() { return getChildAt(pos++); } - @Override public void remove() { throw new UnsupportedOperationException(); } @@ -57,7 +54,6 @@ public TermViewFlipper(Context context, AttributeSet attrs) { this.context = context; } - @Override public Iterator iterator() { return new ViewFlipperIterator(); } diff --git a/src/jackpal/androidterm/WindowList.java b/src/jackpal/androidterm/WindowList.java index 92780c713..447d1bf67 100644 --- a/src/jackpal/androidterm/WindowList.java +++ b/src/jackpal/androidterm/WindowList.java @@ -45,22 +45,18 @@ public class WindowList extends ListActivity { class WindowListAdapter extends BaseAdapter { private LayoutInflater inflater = getLayoutInflater(); - @Override public int getCount() { return sessions.size(); } - @Override public Object getItem(int position) { return sessions.get(position); } - @Override public long getItemId(int position) { return position; } - @Override public View getView(int position, View convertView, ViewGroup parent) { View child = inflater.inflate(R.layout.window_list_item, parent, false); TextView label = (TextView) child.findViewById(R.id.window_list_label); @@ -70,7 +66,6 @@ public View getView(int position, View convertView, ViewGroup parent) { final TermService service = mTermService; final int closePosition = position; close.setOnClickListener(new View.OnClickListener() { - @Override public void onClick(View v) { TermSession session = service.getSessions().remove(closePosition); if (session != null) { @@ -113,14 +108,12 @@ public void setPressed(boolean pressed) { } private ServiceConnection mTSConnection = new ServiceConnection() { - @Override public void onServiceConnected(ComponentName className, IBinder service) { TermService.TSBinder binder = (TermService.TSBinder) service; mTermService = binder.getService(); populateList(); } - @Override public void onServiceDisconnected(ComponentName arg0) { mTermService = null; } From 811696d75b12d2ff705de83e83550ceb58b238e2 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sun, 28 Aug 2011 11:34:21 -0700 Subject: [PATCH 093/847] Handle fractional-pixel-width fixed-width fonts. This should fix the problems people have been having with text rendering when using non-standard fixed-width fonts. --- src/jackpal/androidterm/EmulatorView.java | 14 +++++++------- src/jackpal/androidterm/model/TextRenderer.java | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/jackpal/androidterm/EmulatorView.java b/src/jackpal/androidterm/EmulatorView.java index 25fba9a13..1e3a87de8 100644 --- a/src/jackpal/androidterm/EmulatorView.java +++ b/src/jackpal/androidterm/EmulatorView.java @@ -88,7 +88,7 @@ public class EmulatorView extends View implements GestureDetector.OnGestureListe /** * Total width of each character, in pixels */ - private int mCharacterWidth; + private float mCharacterWidth; /** * Total height of each character, in pixels @@ -892,9 +892,9 @@ protected void onSizeChanged(int w, int h, int oldw, int oldh) { } private void updateSize(int w, int h) { - mColumns = Math.max(1, w / mCharacterWidth); + mColumns = Math.max(1, (int) (((float) w) / mCharacterWidth)); mRows = Math.max(1, h / mCharacterHeight); - mVisibleColumns = mVisibleWidth / mCharacterWidth; + mVisibleColumns = (int) (((float) mVisibleWidth) / mCharacterWidth); mTermSession.updateSize(mColumns, mRows); @@ -1034,7 +1034,7 @@ public Bitmap4x8FontRenderer(Resources resources, mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); } - public int getCharacterWidth() { + public float getCharacterWidth() { return kCharacterWidth; } @@ -1104,7 +1104,7 @@ public PaintRenderer(int fontSize, int forePaintColor, int backPaintColor) { mCharHeight = (int) Math.ceil(mTextPaint.getFontSpacing()); mCharAscent = (int) Math.ceil(mTextPaint.ascent()); mCharDescent = mCharHeight + mCharAscent; - mCharWidth = (int) mTextPaint.measureText(EXAMPLE_CHAR, 0, 1); + mCharWidth = mTextPaint.measureText(EXAMPLE_CHAR, 0, 1); } public void drawTextRun(Canvas canvas, float x, float y, int lineOffset, @@ -1141,13 +1141,13 @@ public int getCharacterHeight() { return mCharHeight; } - public int getCharacterWidth() { + public float getCharacterWidth() { return mCharWidth; } private Paint mTextPaint; - private int mCharWidth; + private float mCharWidth; private int mCharHeight; private int mCharAscent; private int mCharDescent; diff --git a/src/jackpal/androidterm/model/TextRenderer.java b/src/jackpal/androidterm/model/TextRenderer.java index f4e0a7b75..5d9c75488 100644 --- a/src/jackpal/androidterm/model/TextRenderer.java +++ b/src/jackpal/androidterm/model/TextRenderer.java @@ -23,7 +23,7 @@ */ public interface TextRenderer { - int getCharacterWidth(); + float getCharacterWidth(); int getCharacterHeight(); void drawTextRun(Canvas canvas, float x, float y, int lineOffset, char[] text, From 5238daec51c8c3bd2c1deccf5a04f4930979dbe5 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Mon, 29 Aug 2011 16:52:45 -0700 Subject: [PATCH 094/847] Increment version number. --- AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index eec6bc921..d783bad43 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,6 +1,6 @@ From ccfdb925bf1387b73059d80a57b5a6c5867bf59a Mon Sep 17 00:00:00 2001 From: jjsan Date: Tue, 6 Sep 2011 21:36:55 +0200 Subject: [PATCH 095/847] Updated Slovak Language Change-Id: I7de4ff32182bcba7e0379e3316b05e39a2aa50a8 --- res/values-sk/arrays.xml | 81 ++++++++++++++++++++++++++++++++++++++ res/values-sk/strings.xml | 83 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 164 insertions(+) create mode 100644 res/values-sk/arrays.xml create mode 100644 res/values-sk/strings.xml diff --git a/res/values-sk/arrays.xml b/res/values-sk/arrays.xml new file mode 100644 index 000000000..b653487b2 --- /dev/null +++ b/res/values-sk/arrays.xml @@ -0,0 +1,81 @@ + + + + + + Zobraziť stavovú lištu + Skryť stavovú lištu + + + + Kurzor nebliká + Kurzor bliká + + + + Obdĺžnikový + Podčiarknutý + Zvislá čiara + + + + 4 x 8 pixelov + 6 bodov + 7 bodov + 8 bodov + 9 bodov + 10 bodov + 12 bodov + 14 bodov + 16 bodov + 20 bodov + + + + Čierny text na bbielom + Biely text na čiernom + Biely text na modrom + Zelený text na čiernom + Jantárový text na čiernom + Červený text na čiernom + + + + Gulička + Klávesa \@ + Klávesa ľavý Alt + Klávesa pravý Alt + Klávesa Hlas+ + Klávesa Hlas- + Klávesa kamery + + + + Gulička + Klávesa \@ + Klávesa ľavý Alt + Klávesa pravý Alt + Klávesa Hlas+ + Klávesa Hlas- + Klávesa kamery + + + + Po znakoch + Po slovách + + diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml new file mode 100644 index 000000000..0def71ba9 --- /dev/null +++ b/res/values-sk/strings.xml @@ -0,0 +1,83 @@ + + + + Emulácia terminálu + Nastavenia + Obnoviť term. + Poslať e-mailom + Špec. klávesy + Skryť/zobraziť klávesnicu + + Zabrániť uspaniu + Povoliť uspanie + Zabrániť odpojeniu WiFi + Povoliť odpojenie WiFi + + Zmeniť text + Vybrať text + Kopírovať vše + Vložiť + + Terminálová relácia je spustená + + + Obrazovka + + Stavový riadok + Zobraziť/skryť stavový riadok. + Stavový riadok + + Štýl kurzoru + Zvoliť štýl kurzoru. + Štýl kurzoru + Blikanie kurzoru + Zvoliť blikanie kurzoru. + Blikanie kurzoru + + Text + + Veľkosť fontu + Zvoliť výšku znaku v bodoch. + Veľkosť fontu + + Farby + Zvoliť farbu textu. + Farba textu + + Klávesnica + + Riadiaca klávesa + Zvoliť riadiacu klávesu. + Riadiaca klávesa + + Funkčná klávesa + Zvoliť funkčnú klávesu. + Funkčná klávesa + + Metóda vstupu + Zvoliť metódu zadávania pre klávesnicu. + Metóda vstupu + + Shell + Príkazový riadok + Zvoliť príkaz pre shell. + Shell + + Inicializácia + Previesť po spustení shellu. + Inicializácia + From 3a738cdb919f96110bfacacc44be8d9d11741ac7 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sun, 18 Sep 2011 10:09:24 -0700 Subject: [PATCH 096/847] Update private build tool to support NDK r6b --- tools/build-release | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/build-release b/tools/build-release index 95ced8247..08ed926b6 100755 --- a/tools/build-release +++ b/tools/build-release @@ -7,4 +7,4 @@ cd ~/code/Android-Terminal-Emulator rm -rf bin obj cd jni -~/code/android-ndk-r5b/ndk-build && cd .. && ant release && mv bin/Term-release.apk bin/Term.apk +~/code/android-ndk-r6b/ndk-build && cd .. && ant release && mv bin/Term-release.apk bin/Term.apk From 7165bc829b573cfd383693fbcfbe0fa85b228b72 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sun, 18 Sep 2011 10:11:20 -0700 Subject: [PATCH 097/847] Update config file for latest Android SDK --- proguard.cfg | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/proguard.cfg b/proguard.cfg index 12dd0392c..b1cdf17b5 100644 --- a/proguard.cfg +++ b/proguard.cfg @@ -18,14 +18,18 @@ native ; } --keepclasseswithmembernames class * { +-keepclasseswithmembers class * { public (android.content.Context, android.util.AttributeSet); } --keepclasseswithmembernames class * { +-keepclasseswithmembers class * { public (android.content.Context, android.util.AttributeSet, int); } +-keepclassmembers class * extends android.app.Activity { + public void *(android.view.View); +} + -keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); From e543ef97835fe089f9f25e6cb4b0ad61d68982c9 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sun, 18 Sep 2011 10:13:05 -0700 Subject: [PATCH 098/847] Generate native code for x86 as well as armeabi. In theory this will allow Android Terminal Emulator to run on an x86 version of Android. --- jni/Application.mk | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 jni/Application.mk diff --git a/jni/Application.mk b/jni/Application.mk new file mode 100644 index 000000000..f037cdb44 --- /dev/null +++ b/jni/Application.mk @@ -0,0 +1,2 @@ +# Build both ARMv5TE and x86 machine code. +APP_ABI := armeabi x86 From a2148fc60d979cd0607150a2e0c7bc93ca685816 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Wed, 28 Sep 2011 21:42:00 -0700 Subject: [PATCH 099/847] Update Italian translations Courtesy fireb33@gmail.com --- res/values-it/strings.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml index 32d3e5481..e6a729410 100644 --- a/res/values-it/strings.xml +++ b/res/values-it/strings.xml @@ -17,6 +17,11 @@ Emulatore terminale Impostazioni + Nuova finestra + Chiudi finestra + Finestre + Fin. successiva + Fin. precedente Reset terminale Invia email Tasti speciali From f520f558a49a0c17e2304b2681772bc49daf6851 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Wed, 28 Sep 2011 21:56:45 -0700 Subject: [PATCH 100/847] Basque localization. Courtesy asier.iturralde@gmail.com --- res/values-eu/arrays.xml | 180 ++++++++++++++++++++++++++++++++++++++ res/values-eu/strings.xml | 112 ++++++++++++++++++++++++ 2 files changed, 292 insertions(+) create mode 100644 res/values-eu/arrays.xml create mode 100644 res/values-eu/strings.xml diff --git a/res/values-eu/arrays.xml b/res/values-eu/arrays.xml new file mode 100644 index 000000000..1de7f78af --- /dev/null +++ b/res/values-eu/arrays.xml @@ -0,0 +1,180 @@ + + + + + + Bistaratu egoera-barra + Ezkutatu egoera-barra + + + + + 1 + 0 + + + + Kurtsore ez-keinukaria + Kurtsore keinukaria + + + + + 0 + 1 + + + + Laukizuzena + Azpimarra + Barra bertikala + + + + + 0 + 1 + 2 + + + + 4 x 8 pixel + 6 pt + 7 pt + 8 pt + 9 pt + 10 pt + 12 pt + 14 pt + 16 pt + 20 pt + + + + + 0 + 6 + 7 + 8 + 9 + 10 + 12 + 14 + 16 + 20 + + + + Testu beltza zuriaren gainean + Testu zuria beltzaren gainean + Testu zuria urdinaren gainean + Testu berdea beltzaren gainean + Testu anbarra beltzaren gainean + Testu gorria beltzaren gainean + + + + + 0 + 1 + 2 + 3 + 4 + 5 + + + + Jog bola + \@ tekla + Ezker Alt tekla + Eskuin Alt tekla + Bol Igo tekla + Bol Jaitsi tekla + Kamera tekla + Bat ere ez + + + + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + + + + Jog bola + \@ tekla + Ezker Alt tekla + Eskuin Alt tekla + Bol Igo tekla + Bol Jaitsi tekla + Kamera tekla + Bat ere ez + + + + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + + + + Karaktereetan oinarritua + Hitzetan oinarritua + + + + + 0 + 1 + + + + + Bola + \@ + Ezker-Alt + Eskuin-Alt + Bol-Igo + Bol-Jaitsi + Kamera + Bat ere ez + + + + + Bola + \@ + Ezker-Alt + Eskuin-Alt + Bol-Igo + Bol-Jaitsi + Kamera + Bat ere ez + + diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml new file mode 100644 index 000000000..b88bfdb74 --- /dev/null +++ b/res/values-eu/strings.xml @@ -0,0 +1,112 @@ + + + + Terminal Emulatzailea + Hobespenak + Leiho berria + Leihoa itxi + Leihoak + Aurreko leihoa + Hurrengo leihoa + Berrezarri terminala + Bidali eposta ...(r)i + Tekla bereziak + Txandakatu soft teklatua + + Gaitu WakeLock + Desgaitu WakeLock + Gaitu WifiLock + Desgaitu WifiLock + + Editatu testua + Hautatu testua + Kopiatu guztia + Itsatsi + + Terminal saioa martxan dago + + + Pantaila + + Egoera-barra + Bistaratu/ezkutatu egoera-barra. + Egoera-barra + + Kurtsore-estiloa + Hautatu kurtsore-estiloa. + Kurtsore-estiloa + + Kurtsore-keinua + Hautatu kurtsore-keinua. + Kurtsore-keinua + + Testua + + Letra-tamaina + Hautatu karaktereen tamaina puntutan. + Letra-tamaina + + Koloreak + Hautatu testuaren kolorea. + Testuaren kolorea + + Teklatua + + Kontrol tekla + Hautatu kontrol tekla. + Kontrol tekla + + Fn tekla + Hautatu Fn tekla. + Fn tekla + + Sarrera-metodoa + Hautatu sarrera-metodoa soft teklatuarentzat. + Sarrera-metodoa + + Shell + Komando lerroa + Zehaztu shell komando lerroa. + Shell + + Hasierako komandoa + Bidali shell-era abiarazten denean. + Hasierako Komandoa + + + 0 + 0 + 0 + 10 + 2 + 5 + 4 + 0 + /system/bin/sh - + export PATH=/data/local/bin:$PATH + + Kontrol and Funtzio Teklak + + CTRLKEY Zuriunea : Kontrol-@ (NUL)\nCTRLKEY A..Z : Kontrol-A..Z\nCTRLKEY 5 : Kontrol-]\nCTRLKEY 6 : Kontrol-^\nCTRLKEY 7 : Kontrol-_\nCTRLKEY 9 : F11\nCTRLKEY 0 : F12 + Ez da kontrol teklarik ezarri. + + FNKEY 1..9 : F1-F9\nFNKEY 0 : F10\nFNKEY W : Gora\nFNKEY A : Ezkerra\nFNKEY S : Behera\nFNKEY D : Eskuina\nFNKEY P : Hurrengo-orria\nFNKEY N : Aurreko-orria\nFNKEY T : Tabulatzailea\nFNKEY L : | (kanalizazioa)\nFNKEY U : _ (azpimarra)\nFNKEY E : Kontrol-[ (ESC)\nFNKEY X : Ezabatu\nFNKEY I : Txertatu\nFNKEY H : Etxea\nFNKEY F : Bukaera\nFNKEY . : Kontrol-\\\n + Ez da funtzio teklarik ezarri. + + From 59cab73bfe679607a1b2eee0ea2a3e5d6b56c542 Mon Sep 17 00:00:00 2001 From: pvolkov Date: Sat, 1 Oct 2011 11:49:20 +0400 Subject: [PATCH 101/847] Update Russian translation - Terminal Emulator --- res/values-ru/arrays.xml | 22 ++++++++++++++++++++++ res/values-ru/strings.xml | 17 ++++++++++++++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/res/values-ru/arrays.xml b/res/values-ru/arrays.xml index 5be72f1e6..a566e011b 100644 --- a/res/values-ru/arrays.xml +++ b/res/values-ru/arrays.xml @@ -50,6 +50,7 @@ Правый Alt Громкость вверх Громкость вниз + Кнопка камеры @@ -57,4 +58,25 @@ По словам + + Немигающий курсор + Мигающий курсор + + + + Прямоугольный + Подчёркивание + Вертикальная черта + + + + Jog ball + Клавиша \@ + Левый Alt + Правый Alt + Громкость вверх + Громкость вниз + Кнопка камеры + + diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml index ca1fcfb07..2a46f78c2 100644 --- a/res/values-ru/strings.xml +++ b/res/values-ru/strings.xml @@ -21,13 +21,25 @@ Отправит Email Специальные клавиши Экранная клавиатура + Вкл. блокировку пробуждения + Выкл. блокировку пробуждения + Вкл. блокировку Wi-Fi + Выкл. блокировку Wi-Fi Изменить + Выбрать текст Копировать всё Вставить + Сессия терминала запущена Экран Статус бар Показать/Скрыть статус бар. Статус бар + Стиль курсора + Выберите стиль курсора. + Стиль курсора + Мигание курсора + Выберите стиль мигания курсора. + Мигание курсора Текст Размер шрифта Выберите размер шрифта. @@ -37,8 +49,11 @@ Цвет текста Клавиатура Клавиша Control - Выберите что будет клавишей Control. + Выберите, что будет клавишей Control. Клавиша Control + Клавиша Fn + Выберите, что будет клавишей Fn. + Клавиша Fn Способ ввода Выберите способ ввода для экранной клавиатуры. Способ ввода From b87fd19e39bffbc834ab2f7608d8e450473220ea Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Fri, 30 Sep 2011 22:48:08 -0700 Subject: [PATCH 102/847] Optimize getTranscriptText() The current implementation is going through each line character by character, even for lines that are known to be unwanted in the final output; this is silly and easy to fix. Signed-off-by: Jack Palevich --- .../androidterm/session/TranscriptScreen.java | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/src/jackpal/androidterm/session/TranscriptScreen.java b/src/jackpal/androidterm/session/TranscriptScreen.java index b725893c0..4ff0055db 100644 --- a/src/jackpal/androidterm/session/TranscriptScreen.java +++ b/src/jackpal/androidterm/session/TranscriptScreen.java @@ -436,7 +436,13 @@ private String internalGetTranscriptText(boolean stripColors, int selX1, int sel char[] rowBuffer = mRowBuffer; char[] data = mData; int columns = mColumns; - for (int row = -mActiveTranscriptRows; row < mScreenRows; row++) { + if (selY1 < -mActiveTranscriptRows) { + selY1 = -mActiveTranscriptRows; + } + if (selY2 >= mScreenRows) { + selY2 = mScreenRows - 1; + } + for (int row = selY1; row <= selY2; row++) { int offset = getOffset(row); int lastPrintingChar = -1; for (int column = 0; column < columns; column++) { @@ -449,23 +455,21 @@ private String internalGetTranscriptText(boolean stripColors, int selX1, int sel } rowBuffer[column] = c; } - if ( row >= selY1 && row <= selY2 ) { - int x1 = 0; - int x2 = 0; - if ( row == selY1 ) { - x1 = selX1; - } - if ( row == selY2 ) { - x2 = selX2; - } else { - x2 = columns; - } - if (mLineWrap[externalToInternalRow(row)]) { - builder.append(rowBuffer, x1, x2 - x1); - } else { - builder.append(rowBuffer, x1, Math.max(0, Math.min(x2 - x1 + 1, lastPrintingChar + 1 - x1))); - builder.append('\n'); - } + int x1 = 0; + int x2 = 0; + if ( row == selY1 ) { + x1 = selX1; + } + if ( row == selY2 ) { + x2 = selX2; + } else { + x2 = columns; + } + if (mLineWrap[externalToInternalRow(row)]) { + builder.append(rowBuffer, x1, x2 - x1); + } else { + builder.append(rowBuffer, x1, Math.max(0, Math.min(x2 - x1 + 1, lastPrintingChar + 1 - x1))); + builder.append('\n'); } } return builder.toString(); From 7117c41e8bfc2165009b8a3688fecbaaac036f6e Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Fri, 30 Sep 2011 22:48:23 -0700 Subject: [PATCH 103/847] Add a transcript backing store suitable for Unicode text Storing Unicode text in a form suitable for a terminal emulator poses several challenges: * In Java's native UTF-16 encoding, some Unicode code points (those outside the Basic Multilingual Plane) require two chars to express. * The use of combining characters means that it's possible for the character displayed in one column/screen position to require several Unicode code points to express. * Some Unicode code points (particularly East Asian wide characters, which include all of the CJK ideographs) take up two columns/screen positions. The UnicodeTranscript class is designed to make the operations we need most frequently -- storing a character to a particular screen position, and getting all or part of a line -- as fast as possible. The design uses an array of two types of lines: "basic", which is just an array of char[], and is used to store a line as long as it has no combining characters, wide characters, or non-BMP characters; and "full", which is an array of char[] together with an array of offsets allowing us to easily find a particular column in the line. Basic lines in the transcript are automatically converted to full lines as needed. Color/formatting information and line wrapping information are stored in separate arrays. We also expose a static method, charWidth(), which returns the number of columns/screen positions that a particular Unicode code point will use. This information is needed by other parts of the emulator to correctly handle Unicode. Correct support for East Asian wide characters requires Android 2.2 or later -- 2.2 introduced a getEastAsianWidth() method, which we use to determine the width of a character. On older platforms, we just pretend all characters which aren't combining or control characters have width 1. Signed-off-by: Jack Palevich --- .../androidterm/util/UnicodeTranscript.java | 890 ++++++++++++++++++ 1 file changed, 890 insertions(+) create mode 100644 src/jackpal/androidterm/util/UnicodeTranscript.java diff --git a/src/jackpal/androidterm/util/UnicodeTranscript.java b/src/jackpal/androidterm/util/UnicodeTranscript.java new file mode 100644 index 000000000..ce0ef1b45 --- /dev/null +++ b/src/jackpal/androidterm/util/UnicodeTranscript.java @@ -0,0 +1,890 @@ +/* + * Copyright (C) 2011 Steven Luo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jackpal.androidterm.util; + +import android.text.AndroidCharacter; +import android.util.Log; + +/** + * A backing store for a TranscriptScreen. + * + * The text is stored as a circular buffer of rows. There are two types of + * row: + * - "basic", which is a char[] array used to store lines which consist + * entirely of regular-width characters (no combining characters, zero-width + * characters, East Asian double-width characters, etc.) in the BMP; and + * - "full", which is a char[] array with extra trappings which can be used to + * store a line containing any valid Unicode sequence. An array of short[] + * is used to store the "offset" at which each column starts; for example, + * if column 20 starts at index 23 in the array, then mOffset[20] = 3. + * + * Color/formatting information is stored in a separate circular buffer of + * byte[]. The high four bits encode foreground color, while the low four + * bits encode background color. + * + * Rows are allocated on demand, when a character is first stored into them. + * A "basic" row is allocated unless the store which triggers the allocation + * requires a "full" row. "Basic" rows are converted to "full" rows when + * needed. There is no conversion in the other direction -- a "full" row + * stays that way even if it contains only regular-width BMP characters. + */ +public class UnicodeTranscript { + private static final String TAG = "UnicodeTranscript"; + + private Object[] mLines; + private byte[][] mColor; + private boolean[] mLineWrap; + private int mTotalRows; + private int mScreenRows; + private int mColumns; + private int mActiveTranscriptRows = 0; + private int mNextTranscriptRow = 0; + private int mDefaultForeColor = 0; + private int mDefaultBackColor = 0; + + private char[] tmpChar = new char[2]; + private char[] tmpLine; + private byte[] tmpColor; + + public UnicodeTranscript(int columns, int totalRows, int screenRows, int foreColor, int backColor) { + mColumns = columns; + mTotalRows = totalRows; + mScreenRows = screenRows; + mLines = new Object[totalRows]; + mColor = new byte[totalRows][]; + mLineWrap = new boolean[totalRows]; + tmpColor = new byte[columns]; + + mDefaultForeColor = foreColor; + mDefaultBackColor = backColor; + } + + public void setDefaultColors(int foreColor, int backColor) { + mDefaultForeColor = foreColor; + mDefaultBackColor = backColor; + } + + public int getDefaultForeColor() { + return mDefaultForeColor; + } + + public int getDefaultBackColor() { + return mDefaultBackColor; + } + + public int getActiveTranscriptRows() { + return mActiveTranscriptRows; + } + + public int getActiveRows() { + return mActiveTranscriptRows + mScreenRows; + } + + /** + * Convert a row value from the public external coordinate system to our + * internal private coordinate system. + * External coordinate system: + * -mActiveTranscriptRows to mScreenRows-1, with the screen being + * 0..mScreenRows-1 + * Internal coordinate system: 0..mScreenRows-1 is the active screen, + * whereas mScreenRows..mActiveTranscriptRows is the transcript (as a + * circular buffer). + * + * @param extRow a row in the external coordinate system. + * @return The row corresponding to the input argument in the private + * coordinate system. + */ + private int externalToInternalRow(int extRow) { + if (extRow < -mActiveTranscriptRows || extRow > mScreenRows) { + String errorMessage = "externalToInternalRow "+ extRow + + " " + mScreenRows + " " + mActiveTranscriptRows; + Log.e(TAG, errorMessage); + throw new IllegalArgumentException(errorMessage); + } + + if (extRow >= 0) { + return extRow; + } else { + if (mNextTranscriptRow >= -extRow) { + return mScreenRows + mNextTranscriptRow + extRow; + } else { + return mScreenRows + mActiveTranscriptRows + mNextTranscriptRow + extRow; + } + } + } + + public void setLineWrap(int row) { + mLineWrap[externalToInternalRow(row)] = true; + } + + public boolean getLineWrap(int row) { + return mLineWrap[externalToInternalRow(row)]; + } + + /** + * Scroll the screen down one line. To scroll the whole screen of a 24 line + * screen, the arguments would be (0, 24). + * + * @param topMargin First line that is scrolled. + * @param bottomMargin One line after the last line that is scrolled. + */ + public void scroll(int topMargin, int bottomMargin) { + // Separate out reasons so that stack crawls help us + // figure out which condition was violated. + if (topMargin > bottomMargin - 1) { + throw new IllegalArgumentException(); + } + + if (topMargin < 0) { + throw new IllegalArgumentException(); + } + + if (bottomMargin > mScreenRows) { + throw new IllegalArgumentException(); + } + + int screenRows = mScreenRows; + int totalRows = mTotalRows; + int nextTranscriptRow = mNextTranscriptRow; + --bottomMargin; + + /* Save the scrolled line, move the lines below it up one line, then + insert the scrolled line into the transcript */ + Object[] lines = mLines; + byte[][] color = mColor; + boolean[] lineWrap = mLineWrap; + Object scrollLine = lines[topMargin]; + byte[] scrollColor = color[topMargin]; + boolean scrollLineWrap = lineWrap[topMargin]; + System.arraycopy(lines, topMargin + 1, lines, topMargin, bottomMargin - topMargin); + System.arraycopy(color, topMargin + 1, color, topMargin, bottomMargin - topMargin); + System.arraycopy(lineWrap, topMargin + 1, lineWrap, topMargin, bottomMargin - topMargin); + lines[screenRows + nextTranscriptRow] = scrollLine; + color[screenRows + nextTranscriptRow] = scrollColor; + lineWrap[screenRows + nextTranscriptRow] = scrollLineWrap; + ++nextTranscriptRow; + if (screenRows + nextTranscriptRow >= totalRows) { + // Wrap around the circular buffer + nextTranscriptRow = 0; + } + mNextTranscriptRow = nextTranscriptRow; + if (mActiveTranscriptRows + screenRows < totalRows) { + ++mActiveTranscriptRows; + } + + // Make sure the new last line is blank + lines[bottomMargin] = null; + color[bottomMargin] = null; + lineWrap[bottomMargin] = false; + } + + /** + * Block copy characters from one position in the screen to another. The two + * positions can overlap. All characters of the source and destination must + * be within the bounds of the screen, or else an InvalidParameterException + * will be thrown. + * + * @param sx source X coordinate + * @param sy source Y coordinate + * @param w width + * @param h height + * @param dx destination X coordinate + * @param dy destination Y coordinate + */ + public void blockCopy(int sx, int sy, int w, int h, int dx, int dy) { + if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows + || dx < 0 || dx + w > mColumns || dy < 0 + || dy + h > mScreenRows) { + throw new IllegalArgumentException(); + } + Object[] lines = mLines; + byte[][] color = mColor; + if (sy > dy) { + // Move in increasing order + for (int y = 0; y < h; y++) { + int srcRow = externalToInternalRow(sy + y); + int dstRow = externalToInternalRow(dy + y); + if (lines[srcRow] instanceof char[] && lines[dstRow] instanceof char[]) { + System.arraycopy(lines[srcRow], sx, lines[dstRow], dx, w); + } else { + // XXX There has to be a faster way to do this ... + int extDstRow = dy + y; + char[] tmp = getLine(sy + y, sx, sx + w); + if (tmp == null) { + // Source line was blank + blockSet(dx, extDstRow, w, 1, ' ', mDefaultForeColor, mDefaultBackColor); + continue; + } + char cHigh = 0; + int x = 0; + int columns = mColumns; + for (int i = 0; i < tmp.length; ++i) { + if (tmp[i] == 0 || dx + x >= columns) { + break; + } + if (Character.isHighSurrogate(tmp[i])) { + cHigh = tmp[i]; + continue; + } else if (Character.isLowSurrogate(tmp[i])) { + int codePoint = Character.toCodePoint(cHigh, tmp[i]); + setChar(dx + x, extDstRow, codePoint); + x += charWidth(codePoint); + } else { + setChar(dx + x, extDstRow, tmp[i]); + x += charWidth(tmp[i]); + } + } + } + if (color[srcRow] == null && color[dstRow] == null) { + continue; + } else if (color[srcRow] == null && color[dstRow] != null) { + byte defaultColor = encodeColor(mDefaultForeColor, mDefaultBackColor); + for (int x = dx; x < dx + w; ++x) { + color[dstRow][x] = defaultColor; + } + continue; + } else if (color[srcRow] != null && color[dstRow] == null) { + allocateColor(dstRow, mColumns); + } + System.arraycopy(color[srcRow], sx, color[dstRow], dx, w); + } + } else { + // Move in decreasing order + for (int y = 0; y < h; y++) { + int y2 = h - (y + 1); + int srcRow = externalToInternalRow(sy + y2); + int dstRow = externalToInternalRow(dy + y2); + if (lines[srcRow] instanceof char[] && lines[dstRow] instanceof char[]) { + System.arraycopy(lines[srcRow], sx, lines[dstRow], dx, w); + } else { + int extDstRow = dy + y2; + char[] tmp = getLine(sy + y2, sx, sx + w); + if (tmp == null) { + // Source line was blank + blockSet(dx, extDstRow, w, 1, ' ', mDefaultForeColor, mDefaultBackColor); + continue; + } + char cHigh = 0; + int x = 0; + int columns = mColumns; + for (int i = 0; i < tmp.length; ++i) { + if (tmp[i] == 0 || dx + x >= columns) { + break; + } + if (Character.isHighSurrogate(tmp[i])) { + cHigh = tmp[i]; + continue; + } else if (Character.isLowSurrogate(tmp[i])) { + int codePoint = Character.toCodePoint(cHigh, tmp[i]); + setChar(dx + x, extDstRow, codePoint); + x += charWidth(codePoint); + } else { + setChar(dx + x, extDstRow, tmp[i]); + x += charWidth(tmp[i]); + } + } + } + if (color[srcRow] == null && color[dstRow] == null) { + continue; + } else if (color[srcRow] == null && color[dstRow] != null) { + byte defaultColor = encodeColor(mDefaultForeColor, mDefaultBackColor); + for (int x = dx; x < dx + w; ++x) { + color[dstRow][x] = defaultColor; + } + continue; + } else if (color[srcRow] != null && color[dstRow] == null) { + allocateColor(dstRow, mColumns); + } + System.arraycopy(color[srcRow], sx, color[dstRow], dx, w); + } + } + } + + /** + * Block set characters. All characters must be within the bounds of the + * screen, or else and InvalidParemeterException will be thrown. Typically + * this is called with a "val" argument of 32 to clear a block of + * characters. + * + * @param sx source X + * @param sy source Y + * @param w width + * @param h height + * @param val value to set. + */ + public void blockSet(int sx, int sy, int w, int h, int val, + int foreColor, int backColor) { + if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows) { + Log.e(TAG, "illegal arguments! " + sx + " " + sy + " " + w + " " + h + " " + val + " " + mColumns + " " + mScreenRows); + throw new IllegalArgumentException(); + } + + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + setChar(sx + x, sy + y, val, foreColor, backColor); + } + } + } + + /** + * Gives the display width of the code point in a monospace font. + * + * Nonspacing combining marks, format characters, and control characters + * have display width zero. East Asian fullwidth and wide characters + * have display width two. All other characters have display width one. + * + * Known issues: + * - Proper support for East Asian wide characters requires API >= 8. + * - Results are incorrect for individual Hangul jamo (a syllable block + * of jamo should be one unit with width 2). This does not affect + * precomposed Hangul syllables. + * - Assigning all East Asian "ambiguous" characters a width of 1 may not + * be correct if Android renders those characters as wide in East Asian + * context (as the Unicode standard permits). + * + * @param codePoint A Unicode code point. + * @return The display width of the Unicode code point. + */ + public static int charWidth(int codePoint) { + switch (Character.getType(codePoint)) { + case Character.CONTROL: + case Character.FORMAT: + case Character.NON_SPACING_MARK: + case Character.ENCLOSING_MARK: + return 0; + } + + if (Integer.valueOf(android.os.Build.VERSION.SDK) < 8) { + /* No East Asian wide char support when running on Android < 2.2, + because getEastAsianWidth() was introduced in API 8 */ + return 1; + } + + if (Character.charCount(codePoint) == 1) { + // Android's getEastAsianWidth() only works for BMP characters + switch (AndroidCharacter.getEastAsianWidth((char) codePoint)) { + case AndroidCharacter.EAST_ASIAN_WIDTH_FULL_WIDTH: + case AndroidCharacter.EAST_ASIAN_WIDTH_WIDE: + return 2; + } + } else { + // Outside the BMP, only the ideographic planes contain wide chars + switch ((codePoint >> 16) & 0xf) { + case 2: // Supplementary Ideographic Plane + case 3: // Tertiary Ideographic Plane + return 2; + } + } + + return 1; + } + + public static int charWidth(char cHigh, char cLow) { + return charWidth(Character.toCodePoint(cHigh, cLow)); + } + + /** + * Get the contents of a line (or part of a line) of the transcript. + * + * The char[] array returned may be part of the internal representation + * of the line -- make a copy first if you want to modify it. The last + * character requested will be followed by a NUL; the contents of the rest + * of the array could potentially be garbage. + * + * @param row The row number to get (-mActiveTranscriptRows..mScreenRows-1) + * @param x1 The first screen position that's wanted + * @param x2 One after the last screen position that's wanted + * @return A char[] array containing the requested contents + */ + public char[] getLine(int row, int x1, int x2) { + if (row < -mActiveTranscriptRows || row > mScreenRows-1) { + throw new IllegalArgumentException(); + } + + int columns = mColumns; + row = externalToInternalRow(row); + if (mLines[row] == null) { + // Line is blank + return null; + } + if (mLines[row] instanceof char[]) { + // Line contains only regular-width BMP characters + if (x1 == 0 && x2 == columns) { + // Want the whole row? Easy. + return (char[]) mLines[row]; + } else { + if (tmpLine == null || tmpLine.length < columns + 1) { + tmpLine = new char[columns+1]; + } + int length = x2 - x1; + System.arraycopy(mLines[row], x1, tmpLine, 0, length); + tmpLine[length] = 0; + return tmpLine; + } + } + + // Figure out how long the array needs to be + FullUnicodeLine line = (FullUnicodeLine) mLines[row]; + char[] rawLine = line.getLine(); + x1 = line.findStartOfColumn(x1); + if (x2 < columns) { + x2 = line.findStartOfColumn(x2); + } else { + x2 = line.getSpaceUsed(); + } + int length = x2 - x1; + + if (tmpLine == null || tmpLine.length < length + 1) { + tmpLine = new char[length+1]; + } + System.arraycopy(rawLine, x1, tmpLine, 0, length); + tmpLine[length] = 0; + return tmpLine; + } + + public char[] getLine(int row) { + return getLine(row, 0, mColumns); + } + + public byte[] getLineColor(int row, int x1, int x2) { + if (row < -mActiveTranscriptRows || row > mScreenRows-1) { + throw new IllegalArgumentException(); + } + + row = externalToInternalRow(row); + if (mColor[row] != null) { + if (x1 == 0 && x2 == mColumns) { + return mColor[row]; + } + + System.arraycopy(mColor[row], x1, tmpColor, 0, x2-x1); + return tmpColor; + } else { + return null; + } + } + + public byte[] getLineColor(int row) { + return getLineColor(row, 0, mColumns); + } + + public boolean getChar(int row, int column) { + return getChar(row, column, 0); + } + + public boolean getChar(int row, int column, int charIndex) { + return getChar(row, column, charIndex, new char[1], 0); + } + + /** + * Get a character at a specific position in the transcript. + * + * @param row The row of the character to get. + * @param column The column of the character to get. + * @param charIndex The index of the character in the column to get (0 for the first character, 1 for the next, etc.) + * @param out The char[] array into which the character will be placed. + * @param offset The offset in the array at which the character will be placed. + * @return Whether or not there are characters following this one in the column. + */ + public boolean getChar(int row, int column, int charIndex, char[] out, int offset) { + if (row < -mActiveTranscriptRows || row > mScreenRows-1) { + throw new IllegalArgumentException(); + } + row = externalToInternalRow(row); + + if (mLines[row] instanceof char[]) { + // Fast path: all regular-width BMP chars in the row + char[] line = (char[]) mLines[row]; + out[offset] = line[column]; + return false; + } + + FullUnicodeLine line = (FullUnicodeLine) mLines[row]; + char[] rawLine = line.getLine(); + int pos = line.findStartOfColumn(column); + int length; + if (column + 1 < mColumns) { + length = line.findStartOfColumn(column + 1) - pos; + } else { + length = line.getSpaceUsed() - pos; + } + if (charIndex >= length) { + throw new IllegalArgumentException(); + } + out[offset] = rawLine[pos + charIndex]; + return (charIndex + 1 < length); + } + + public int getForeColor(int row, int column) { + if (row < -mActiveTranscriptRows || row > mScreenRows-1) { + throw new IllegalArgumentException(); + } + row = externalToInternalRow(row); + + if (mColor[row] == null) { + return mDefaultForeColor; + } else { + return (mColor[row][column] >> 4) & 0xf; + } + } + + public int getBackColor(int row, int column) { + if (row < -mActiveTranscriptRows || row > mScreenRows-1) { + throw new IllegalArgumentException(); + } + row = externalToInternalRow(row); + + if (mColor[row] == null) { + return mDefaultBackColor; + } else { + return mColor[row][column] & 0xf; + } + } + + private boolean isBasicChar(int codePoint) { + return !(charWidth(codePoint) != 1 || Character.charCount(codePoint) != 1); + } + + private char[] allocateBasicLine(int row, int columns) { + char[] line = new char[columns]; + + // Fill the line with blanks + for (int i = 0; i < columns; ++i) { + line[i] = ' '; + } + + mLines[row] = line; + mColor[row] = null; + return line; + } + + private FullUnicodeLine allocateFullLine(int row, int columns) { + FullUnicodeLine line = new FullUnicodeLine(columns); + + mLines[row] = line; + mColor[row] = null; + return line; + } + + private byte[] allocateColor(int row, int columns) { + byte[] color = new byte[columns]; + + // Set all of the columns to the default colors + byte defaultColor = encodeColor(mDefaultForeColor, mDefaultBackColor); + for (int i = 0; i < columns; ++i) { + color[i] = defaultColor; + } + mColor[row] = color; + return color; + } + + private byte encodeColor(int foreColor, int backColor) { + return (byte) (((foreColor & 0xf) << 4) | (backColor & 0xf)); + } + + public boolean setChar(int column, int row, int codePoint, int foreColor, int backColor) { + if (!setChar(column, row, codePoint)) { + return false; + } + + row = externalToInternalRow(row); + if (mColor[row] == null) { + allocateColor(row, mColumns); + } + mColor[row][column] = encodeColor(foreColor, backColor); + + return true; + } + + public boolean setChar(int column, int row, int codePoint) { + if (row >= mScreenRows || column >= mColumns) { + Log.e(TAG, "illegal arguments! " + row + " " + column + " " + mScreenRows + " " + mColumns); + throw new IllegalArgumentException(); + } + row = externalToInternalRow(row); + + /* + * Whether data contains non-BMP or characters with charWidth != 1 + * 0 - false; 1 - true; -1 - undetermined + */ + int basicMode = -1; + + // Allocate a row on demand + if (mLines[row] == null) { + if (isBasicChar(codePoint)) { + allocateBasicLine(row, mColumns); + basicMode = 1; + } else { + allocateFullLine(row, mColumns); + basicMode = 0; + } + } + + if (mLines[row] instanceof char[]) { + char[] line = (char[]) mLines[row]; + + if (basicMode == -1) { + if (isBasicChar(codePoint)) { + basicMode = 1; + } else { + basicMode = 0; + } + } + + if (basicMode == 1) { + // Fast path -- just put the char in the array + line[column] = (char) codePoint; + return true; + } + + // Need to switch to the full-featured mode + mLines[row] = new FullUnicodeLine(line); + } + + FullUnicodeLine line = (FullUnicodeLine) mLines[row]; + line.setChar(column, codePoint); + return true; + } +} + +/* + * A representation of a line that's capable of handling non-BMP characters, + * East Asian wide characters, and combining characters. + * + * The text of the line is stored in an array of char[], allowing easy + * conversion to a String and/or reuse by other string-handling functions. + * An array of short[] is used to keep track of the difference between a column + * and the starting index corresponding to its contents in the char[] array (so + * if column 42 starts at index 45 in the char[] array, the offset stored is 3). + * Column 0 always starts at index 0 in the char[] array, so we use that + * element of the array to keep track of how much of the char[] array we're + * using at the moment. + */ +class FullUnicodeLine { + private static final float SPARE_CAPACITY_FACTOR = 1.5f; + + private char[] mText; + private short[] mOffset; + private int mColumns; + + public FullUnicodeLine(int columns) { + commonConstructor(columns); + char[] text = mText; + // Fill in the line with blanks + for (int i = 0; i < columns; ++i) { + text[i] = ' '; + } + // Store the space used + mOffset[0] = (short) columns; + } + + public FullUnicodeLine(char[] basicLine) { + commonConstructor(basicLine.length); + System.arraycopy(basicLine, 0, mText, 0, mColumns); + // Store the space used + mOffset[0] = (short) basicLine.length; + } + + private void commonConstructor(int columns) { + mColumns = columns; + mOffset = new short[columns]; + mText = new char[(int)(SPARE_CAPACITY_FACTOR*columns)]; + } + + public int getSpaceUsed() { + return mOffset[0]; + } + + public char[] getLine() { + return mText; + } + + public int findStartOfColumn(int column) { + if (column == 0) { + return 0; + } else { + return column + mOffset[column]; + } + } + + public void setChar(int column, int codePoint) { + int columns = mColumns; + if (column < 0 || column >= columns) { + throw new IllegalArgumentException(); + } + + char[] text = mText; + short[] offset = mOffset; + int spaceUsed = offset[0]; + + int pos = findStartOfColumn(column); + + int charWidth = UnicodeTranscript.charWidth(codePoint); + int oldCharWidth; + if (Character.isHighSurrogate(text[pos])) { + oldCharWidth = UnicodeTranscript.charWidth(Character.toCodePoint(text[pos], text[pos+1])); + } else { + oldCharWidth = UnicodeTranscript.charWidth(text[pos]); + } + + // Get the number of elements in the mText array this column uses now + int oldLen; + if (column + oldCharWidth < columns) { + oldLen = findStartOfColumn(column+oldCharWidth) - pos; + } else { + oldLen = spaceUsed - pos; + } + + // Find how much space this column will need + int newLen = Character.charCount(codePoint); + if (charWidth == 0) { + /* Combining characters are added to the contents of the column + instead of overwriting them, so that they modify the existing + contents */ + newLen += oldLen; + } + int shift = newLen - oldLen; + + // Shift the rest of the line right to make room if necessary + if (shift > 0) { + if (spaceUsed + shift > text.length) { + // We need to grow the array + char[] newText = new char[text.length + columns]; + System.arraycopy(text, 0, newText, 0, pos); + System.arraycopy(text, pos + oldLen, newText, pos + newLen, spaceUsed - pos - oldLen); + mText = text = newText; + } else { + System.arraycopy(text, pos + oldLen, text, pos + newLen, spaceUsed - pos - oldLen); + } + } + + // Store the character + if (charWidth > 0) { + Character.toChars(codePoint, text, pos); + } else { + /* Store a combining character at the end of the existing contents, + so that it modifies them */ + Character.toChars(codePoint, text, pos + oldLen); + } + + // Shift the rest of the line left to eliminate gaps if necessary + if (shift < 0) { + System.arraycopy(text, pos + oldLen, text, pos + newLen, spaceUsed - pos - oldLen); + } + + // Update space used + if (shift != 0) { + spaceUsed += shift; + offset[0] = (short) spaceUsed; + } + + /* + * Handle cases where charWidth changes + * width 1 -> width 2: should clobber the contents of the next + * column (if next column contains wide char, need to pad with a space) + * width 2 -> width 1: pad with a space after the new character + */ + if (oldCharWidth == 2 && charWidth == 1) { + // Pad with a space + int nextPos = pos + newLen; + if (spaceUsed + 1 > text.length) { + // Array needs growing + char[] newText = new char[text.length + columns]; + System.arraycopy(text, 0, newText, 0, nextPos); + + System.arraycopy(text, nextPos, newText, nextPos + 1, spaceUsed - nextPos); + mText = text = newText; + } else { + System.arraycopy(text, nextPos, text, nextPos + 1, spaceUsed - nextPos); + } + text[nextPos] = ' '; + + // Update space used + ++offset[0]; + + // Correct the offset for the next column to reflect width change + if (column == 0) { + offset[1] = (short) (newLen - 1); + } else { + offset[column + 1] = (short) (offset[column] + newLen - 1); + } + ++column; + ++shift; + } else if (oldCharWidth == 1 && charWidth == 2) { + if (column == columns - 1) { + // A width 2 character doesn't fit in the last column. + text[pos] = ' '; + offset[0] = (short) (pos + 1); + shift = 0; + } else if (column == columns - 2) { + // Correct offset for the next column to reflect width change + offset[column + 1] = (short) (offset[column] - 1); + + // Truncate the line after this character. + offset[0] = (short) (pos + newLen); + shift = 0; + } else { + // Overwrite the contents of the next column. + int nextPos = pos + newLen; + int nextWidth; + if (Character.isHighSurrogate(text[nextPos])) { + nextWidth = UnicodeTranscript.charWidth(Character.toCodePoint(text[nextPos], text[nextPos+1])); + } else { + nextWidth = UnicodeTranscript.charWidth(text[nextPos]); + } + int nextLen; + if (column + nextWidth + 1 < columns) { + nextLen = findStartOfColumn(column + nextWidth + 1) - nextPos; + } else { + nextLen = spaceUsed - nextPos; + } + + if (nextWidth == 2) { + text[nextPos] = ' '; + // Shift the array to match + if (nextLen > 1) { + System.arraycopy(text, nextPos + nextLen, text, nextPos + 1, spaceUsed - nextPos - nextLen); + shift -= nextLen - 1; + offset[0] -= nextLen - 1; + } + } else { + // Shift the array leftwards + System.arraycopy(text, nextPos + nextLen, text, nextPos, spaceUsed - nextPos - nextLen); + shift -= nextLen; + + // Truncate the line + offset[0] = (short) findStartOfColumn(columns - 1); + } + + // Correct the offset for the next column to reflect width change + if (column == 0) { + offset[1] = -1; + } else { + offset[column + 1] = (short) (offset[column] - 1); + } + ++column; + } + } + + // Update offset table + if (shift != 0) { + for (int i = column + 1; i < columns; ++i) { + offset[i] += shift; + } + } + } +} From e91bb8924d7d5711251b6e02bf9010f33622d56e Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Fri, 30 Sep 2011 22:48:35 -0700 Subject: [PATCH 104/847] Support displaying Unicode text The current scheme, whereby we store the contents of our screen in an array of char (two bytes in Java) with the upper byte used to encode color and formatting information, is fundamentally incompatible with Unicode, where the available encodings all either need more than one byte per character or are variable-length encodings. Instead, we use a new UnicodeTranscript class, which stores the transcript row by row and includes facilities to handle combining characters and East Asian wide characters. We also need to make some minor changes to the code which displays text on screen to support Unicode output. Signed-off-by: Jack Palevich --- src/jackpal/androidterm/EmulatorView.java | 9 +- src/jackpal/androidterm/model/Screen.java | 13 +- .../androidterm/model/TextRenderer.java | 2 +- .../androidterm/session/TerminalEmulator.java | 11 +- .../androidterm/session/TranscriptScreen.java | 382 ++++++------------ 5 files changed, 151 insertions(+), 266 deletions(-) diff --git a/src/jackpal/androidterm/EmulatorView.java b/src/jackpal/androidterm/EmulatorView.java index 1e3a87de8..fbb3a21d5 100644 --- a/src/jackpal/androidterm/EmulatorView.java +++ b/src/jackpal/androidterm/EmulatorView.java @@ -1043,7 +1043,7 @@ public int getCharacterHeight() { } public void drawTextRun(Canvas canvas, float x, float y, - int lineOffset, char[] text, int index, int count, + int lineOffset, int runWidth, char[] text, int index, int count, boolean cursor, int foreColor, int backColor) { setColorMatrix(mForePaint[foreColor & 7], cursor ? mCursorPaint : mBackPaint[backColor & 7]); @@ -1054,7 +1054,8 @@ public void drawTextRun(Canvas canvas, float x, float y, destRect.top = (destY - kCharacterHeight); destRect.bottom = destY; for(int i = 0; i < count; i++) { - char c = text[i + index]; + // XXX No Unicode support in bitmap font + char c = (char) (text[i + index] & 0xff); if ((cursor || (c != 32)) && (c < 128)) { int cellX = c & 31; int cellY = (c >> 5) & 3; @@ -1108,7 +1109,7 @@ public PaintRenderer(int fontSize, int forePaintColor, int backPaintColor) { } public void drawTextRun(Canvas canvas, float x, float y, int lineOffset, - char[] text, int index, int count, + int runWidth, char[] text, int index, int count, boolean cursor, int foreColor, int backColor) { if (cursor) { mTextPaint.setColor(mCursorPaint); @@ -1117,7 +1118,7 @@ public void drawTextRun(Canvas canvas, float x, float y, int lineOffset, } float left = x + lineOffset * mCharWidth; canvas.drawRect(left, y + mCharAscent, - left + count * mCharWidth, y + mCharDescent, + left + runWidth * mCharWidth, y + mCharDescent, mTextPaint); boolean bold = ( foreColor & 0x8 ) != 0; boolean underline = (backColor & 0x8) != 0; diff --git a/src/jackpal/androidterm/model/Screen.java b/src/jackpal/androidterm/model/Screen.java index 6ceb45251..eddd7bd43 100644 --- a/src/jackpal/androidterm/model/Screen.java +++ b/src/jackpal/androidterm/model/Screen.java @@ -29,6 +29,17 @@ public interface Screen { */ void setLineWrap(int row); + /** + * Store a Unicode code point into the screen at location (x, y) + * + * @param x X coordinate (also known as column) + * @param y Y coordinate (also known as row) + * @param codePoint Unicode code point to store + * @param foreColor the foreground color + * @param backColor the background color + */ + void set(int x, int y, int codePoint, int foreColor, int backColor); + /** * Store byte b into the screen at location (x, y) * @@ -47,7 +58,7 @@ public interface Screen { * @param topMargin First line that is scrolled. * @param bottomMargin One line after the last line that is scrolled. */ - void scroll(int topMargin, int bottomMargin, int foreColor, int backColor); + void scroll(int topMargin, int bottomMargin); /** * Block copy characters from one position in the screen to another. The two diff --git a/src/jackpal/androidterm/model/TextRenderer.java b/src/jackpal/androidterm/model/TextRenderer.java index 5d9c75488..a064b266f 100644 --- a/src/jackpal/androidterm/model/TextRenderer.java +++ b/src/jackpal/androidterm/model/TextRenderer.java @@ -26,6 +26,6 @@ public interface TextRenderer { float getCharacterWidth(); int getCharacterHeight(); void drawTextRun(Canvas canvas, float x, float y, - int lineOffset, char[] text, + int lineOffset, int runWidth, char[] text, int index, int count, boolean cursor, int foreColor, int backColor); } diff --git a/src/jackpal/androidterm/session/TerminalEmulator.java b/src/jackpal/androidterm/session/TerminalEmulator.java index 6e60106aa..3e0d19a0a 100644 --- a/src/jackpal/androidterm/session/TerminalEmulator.java +++ b/src/jackpal/androidterm/session/TerminalEmulator.java @@ -305,9 +305,13 @@ public void updateSize(int columns, int rows) { while ((end >= 0) && transcriptText.charAt(end) == '\n') { end--; } + char c, cLow; for(int i = 0; i <= end; i++) { - byte c = (byte) transcriptText.charAt(i); - if (c == '\n') { + c = transcriptText.charAt(i); + if (Character.isHighSurrogate(c)) { + cLow = transcriptText.charAt(++i); + emit(Character.toCodePoint(c, cLow)); + } else if (c == '\n') { setCursorCol(0); doLinefeed(); } else { @@ -1022,8 +1026,7 @@ private void write(byte[] data) { private void scroll() { //System.out.println("Scroll(): mTopMargin " + mTopMargin + " mBottomMargin " + mBottomMargin); mScrollCounter ++; - mScreen.scroll(mTopMargin, mBottomMargin, - getForeColor(), getBackColor()); + mScreen.scroll(mTopMargin, mBottomMargin); } /** diff --git a/src/jackpal/androidterm/session/TranscriptScreen.java b/src/jackpal/androidterm/session/TranscriptScreen.java index 4ff0055db..f799046f1 100644 --- a/src/jackpal/androidterm/session/TranscriptScreen.java +++ b/src/jackpal/androidterm/session/TranscriptScreen.java @@ -16,11 +16,14 @@ package jackpal.androidterm.session; +import java.util.Arrays; + import android.graphics.Canvas; import android.util.Log; import jackpal.androidterm.model.Screen; import jackpal.androidterm.model.TextRenderer; +import jackpal.androidterm.util.UnicodeTranscript; /** * A TranscriptScreen is a screen that remembers data that's been scrolled. The @@ -42,49 +45,12 @@ public class TranscriptScreen implements Screen { */ private int mTotalRows; - /** - * The number of rows in the active portion of the transcript. Doesn't - * include the screen. - */ - private int mActiveTranscriptRows; - - /** - * Which row is currently the topmost line of the transcript. Used to - * implement a circular buffer. - */ - private int mHead; - - /** - * The number of active rows, includes both the transcript and the screen. - */ - private int mActiveRows; - /** * The number of rows in the screen. */ private int mScreenRows; - /** - * The data for both the screen and the transcript. The first mScreenRows * - * mLineWidth characters are the screen, the rest are the transcript. - * The low byte encodes the ASCII character, the high byte encodes the - * foreground and background colors, plus underline and bold. - */ - private char[] mData; - - /** - * The data's stored as color-encoded chars, but the drawing routines require chars, so we - * need a temporary buffer to hold a row's worth of characters. - */ - private char[] mRowBuffer; - - /** - * Flags that keep track of whether the current line logically wraps to the - * next line. This is used when resizing the screen and when copying to the - * clipboard or an email attachment - */ - - private boolean[] mLineWrap; + private UnicodeTranscript mData; /** * Create a transcript screen. @@ -103,16 +69,9 @@ public TranscriptScreen(int columns, int totalRows, int screenRows, private void init(int columns, int totalRows, int screenRows, int foreColor, int backColor) { mColumns = columns; mTotalRows = totalRows; - mActiveTranscriptRows = 0; - mHead = 0; - mActiveRows = screenRows; mScreenRows = screenRows; - int totalSize = columns * totalRows; - mData = new char[totalSize]; - blockSet(0, 0, mColumns, mScreenRows, ' ', foreColor, backColor); - mRowBuffer = new char[columns]; - mLineWrap = new boolean[totalRows]; - consistencyCheck(); + mData = new UnicodeTranscript(columns, totalRows, screenRows, foreColor, backColor); + mData.blockSet(0, 0, mColumns, mScreenRows, ' ', foreColor, backColor); } public void finish() { @@ -124,63 +83,27 @@ public void finish() { * memory being leaked down. */ mData = null; - mRowBuffer = null; - mLineWrap = null; - } - - /** - * Convert a row value from the public external coordinate system to our - * internal private coordinate system. External coordinate system: - * -mActiveTranscriptRows to mScreenRows-1, with the screen being - * 0..mScreenRows-1 Internal coordinate system: 0..mScreenRows-1 rows of - * mData are the visible rows. mScreenRows..mActiveRows - 1 are the - * transcript, stored as a circular buffer. - * - * @param row a row in the external coordinate system. - * @return The row corresponding to the input argument in the private - * coordinate system. - */ - private int externalToInternalRow(int row) { - if (row < -mActiveTranscriptRows || row >= mScreenRows) { - String errorMessage = "externalToInternalRow "+ row + - " " + mActiveTranscriptRows + " " + mScreenRows; - Log.e(TAG, errorMessage); - throw new IllegalArgumentException(errorMessage); - } - if (row >= 0) { - return row; // This is a visible row. - } - return mScreenRows - + ((mHead + mActiveTranscriptRows + row) % mActiveTranscriptRows); - } - - private int getOffset(int externalLine) { - return externalToInternalRow(externalLine) * mColumns; - } - - private int getOffset(int x, int y) { - return getOffset(y) + x; } public void setLineWrap(int row) { - mLineWrap[externalToInternalRow(row)] = true; + mData.setLineWrap(row); } /** - * Store byte b into the screen at location (x, y) + * Store a Unicode code point into the screen at location (x, y) * * @param x X coordinate (also known as column) * @param y Y coordinate (also known as row) - * @param b ASCII character to store + * @param codePoint Unicode codepoint to store * @param foreColor the foreground color * @param backColor the background color */ - public void set(int x, int y, byte b, int foreColor, int backColor) { - mData[getOffset(x, y)] = encode(b, foreColor, backColor); + public void set(int x, int y, int codePoint, int foreColor, int backColor) { + mData.setChar(x, y, codePoint, foreColor, backColor); } - private char encode(int b, int foreColor, int backColor) { - return (char) ((foreColor << 12) | (backColor << 8) | b); + public void set(int x, int y, byte b, int foreColor, int backColor) { + mData.setChar(x, y, b, foreColor, backColor); } /** @@ -190,89 +113,8 @@ private char encode(int b, int foreColor, int backColor) { * @param topMargin First line that is scrolled. * @param bottomMargin One line after the last line that is scrolled. */ - public void scroll(int topMargin, int bottomMargin, int foreColor, - int backColor) { - // Separate out reasons so that stack crawls help us - // figure out which condition was violated. - if (topMargin > bottomMargin - 1) { - throw new IllegalArgumentException(); - } - - if (topMargin > mScreenRows - 1) { - throw new IllegalArgumentException(); - } - - if (bottomMargin > mScreenRows) { - throw new IllegalArgumentException(); - } - - // Adjust the transcript so that the last line of the transcript - // is ready to receive the newly scrolled data - consistencyCheck(); - int expansionRows = Math.min(1, mTotalRows - mActiveRows); - int rollRows = 1 - expansionRows; - mActiveRows += expansionRows; - mActiveTranscriptRows += expansionRows; - if (mActiveTranscriptRows > 0) { - mHead = (mHead + rollRows) % mActiveTranscriptRows; - } - consistencyCheck(); - - // Block move the scroll line to the transcript - int topOffset = getOffset(topMargin); - int destOffset = getOffset(-1); - System.arraycopy(mData, topOffset, mData, destOffset, mColumns); - - int topLine = externalToInternalRow(topMargin); - int destLine = externalToInternalRow(-1); - System.arraycopy(mLineWrap, topLine, mLineWrap, destLine, 1); - - // Block move the scrolled data up - int numScrollChars = (bottomMargin - topMargin - 1) * mColumns; - System.arraycopy(mData, topOffset + mColumns, mData, topOffset, - numScrollChars); - int numScrollLines = (bottomMargin - topMargin - 1); - System.arraycopy(mLineWrap, topLine + 1, mLineWrap, topLine, - numScrollLines); - - // Erase the bottom line of the scroll region - blockSet(0, bottomMargin - 1, mColumns, 1, ' ', foreColor, backColor); - mLineWrap[externalToInternalRow(bottomMargin-1)] = false; - } - - private void consistencyCheck() { - checkPositive(mColumns); - checkPositive(mTotalRows); - checkRange(0, mActiveTranscriptRows, mTotalRows); - if (mActiveTranscriptRows == 0) { - checkEqual(mHead, 0); - } else { - checkRange(0, mHead, mActiveTranscriptRows-1); - } - checkEqual(mScreenRows + mActiveTranscriptRows, mActiveRows); - checkRange(0, mScreenRows, mTotalRows); - - checkEqual(mTotalRows, mLineWrap.length); - checkEqual(mTotalRows*mColumns, mData.length); - checkEqual(mColumns, mRowBuffer.length); - } - - private void checkPositive(int n) { - if (n < 0) { - throw new IllegalArgumentException("checkPositive " + n); - } - } - - private void checkRange(int a, int b, int c) { - if (a > b || b > c) { - throw new IllegalArgumentException("checkRange " + a + " <= " + b + " <= " + c); - } - } - - private void checkEqual(int a, int b) { - if (a != b) { - throw new IllegalArgumentException("checkEqual " + a + " == " + b); - } + public void scroll(int topMargin, int bottomMargin) { + mData.scroll(topMargin, bottomMargin); } /** @@ -289,27 +131,7 @@ private void checkEqual(int a, int b) { * @param dy destination Y coordinate */ public void blockCopy(int sx, int sy, int w, int h, int dx, int dy) { - if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows - || dx < 0 || dx + w > mColumns || dy < 0 - || dy + h > mScreenRows) { - throw new IllegalArgumentException(); - } - if (sy > dy) { - // Move in increasing order - for (int y = 0; y < h; y++) { - int srcOffset = getOffset(sx, sy + y); - int dstOffset = getOffset(dx, dy + y); - System.arraycopy(mData, srcOffset, mData, dstOffset, w); - } - } else { - // Move in decreasing order - for (int y = 0; y < h; y++) { - int y2 = h - (y + 1); - int srcOffset = getOffset(sx, sy + y2); - int dstOffset = getOffset(dx, dy + y2); - System.arraycopy(mData, srcOffset, mData, dstOffset, w); - } - } + mData.blockCopy(sx, sy, w, h, dx, dy); } /** @@ -326,17 +148,7 @@ public void blockCopy(int sx, int sy, int w, int h, int dx, int dy) { */ public void blockSet(int sx, int sy, int w, int h, int val, int foreColor, int backColor) { - if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows) { - throw new IllegalArgumentException(); - } - char[] data = mData; - char encodedVal = encode(val, foreColor, backColor); - for (int y = 0; y < h; y++) { - int offset = getOffset(sx, sy + y); - for (int x = 0; x < w; x++) { - data[offset + x] = encodedVal; - } - } + mData.blockSet(sx, sy, w, h, val, foreColor, backColor); } /** @@ -354,53 +166,109 @@ public void blockSet(int sx, int sy, int w, int h, int val, */ public final void drawText(int row, Canvas canvas, float x, float y, TextRenderer renderer, int cx, int selx1, int selx2, String imeText) { - - // Out-of-bounds rows are blank. - if (row < -mActiveTranscriptRows || row >= mScreenRows) { + char[] line; + byte[] color; + try { + line = mData.getLine(row); + color = mData.getLineColor(row); + } catch (IllegalArgumentException e) { + // Out-of-bounds rows are blank. return; } + int defaultForeColor = mData.getDefaultForeColor(); + int defaultBackColor = mData.getDefaultBackColor(); + + if (line == null) { + // Line is blank. + if (selx1 != selx2) { + // We need to draw a selection + char[] blank = new char[selx2-selx1]; + Arrays.fill(blank, ' '); + renderer.drawTextRun(canvas, x, y, selx1, selx2-selx1, + blank, 0, 1, true, + defaultForeColor, defaultBackColor); + } else if (cx != -1) { + // We need to draw the cursor + renderer.drawTextRun(canvas, x, y, cx, 1, + " ".toCharArray(), 0, 1, true, + defaultForeColor, defaultBackColor); + } - // Copy the data from the byte array to a char array so they can - // be drawn. + return; + } - int offset = getOffset(row); - char[] rowBuffer = mRowBuffer; - char[] data = mData; int columns = mColumns; - int lastColors = 0; + int lastForeColor = 0; + int lastBackColor = 0; + int runWidth = 0; int lastRunStart = -1; + int lastRunStartIndex = -1; + boolean forceFlushRun = false; + char cHigh = 0; final int CURSOR_MASK = 0x10000; - for (int i = 0; i < columns; i++) { - char c = data[offset + i]; - int colors = (char) (c & 0xff00); - if (cx == i || (i >= selx1 && i <= selx2)) { + int column = 0; + int index = 0; + while (column < columns) { + int foreColor, backColor; + if (color != null) { + foreColor = (color[column] >> 4) & 0xf; + backColor = color[column] & 0xf; + } else { + foreColor = defaultForeColor; + backColor = defaultBackColor; + } + int width; + if (Character.isHighSurrogate(line[index])) { + cHigh = line[index++]; + continue; + } else if (Character.isLowSurrogate(line[index])) { + width = UnicodeTranscript.charWidth(cHigh, line[index]); + } else { + width = UnicodeTranscript.charWidth(line[index]); + } + if (cx == column || (column >= selx1 && column <= selx2)) { // Set cursor background color: - colors |= CURSOR_MASK; + backColor |= CURSOR_MASK; } - rowBuffer[i] = (char) (c & 0x00ff); - if (colors != lastColors) { + if (foreColor != lastForeColor || backColor != lastBackColor || (width > 0 && forceFlushRun)) { if (lastRunStart >= 0) { - renderer.drawTextRun(canvas, x, y, lastRunStart, rowBuffer, - lastRunStart, i - lastRunStart, - (lastColors & CURSOR_MASK) != 0, - 0xf & (lastColors >> 12), 0xf & (lastColors >> 8)); + renderer.drawTextRun(canvas, x, y, lastRunStart, runWidth, + line, + lastRunStartIndex, index - lastRunStartIndex, + (lastBackColor & CURSOR_MASK) != 0, + lastForeColor, lastBackColor); } - lastColors = colors; - lastRunStart = i; + lastForeColor = foreColor; + lastBackColor = backColor; + runWidth = 0; + lastRunStart = column; + lastRunStartIndex = index; + forceFlushRun = false; + } + runWidth += width; + column += width; + index++; + if (width > 1) { + /* We cannot draw two or more East Asian wide characters in the + same run, because we need to make each wide character take + up two columns, which may not match the font's idea of the + character width */ + forceFlushRun = true; } } if (lastRunStart >= 0) { - renderer.drawTextRun(canvas, x, y, lastRunStart, rowBuffer, - lastRunStart, columns - lastRunStart, - (lastColors & CURSOR_MASK) != 0, - 0xf & (lastColors >> 12), 0xf & (lastColors >> 8)); + renderer.drawTextRun(canvas, x, y, lastRunStart, runWidth, + line, + lastRunStartIndex, index - lastRunStartIndex, + (lastBackColor & CURSOR_MASK) != 0, + lastForeColor, lastBackColor); } if (cx >= 0 && imeText.length() > 0) { int imeLength = Math.min(columns, imeText.length()); int imeOffset = imeText.length() - imeLength; int imePosition = Math.min(cx, columns - imeLength); - renderer.drawTextRun(canvas, x, y, imePosition, imeText.toCharArray(), + renderer.drawTextRun(canvas, x, y, imePosition, imeLength, imeText.toCharArray(), imeOffset, imeLength, true, 0x0f, 0x00); } } @@ -411,7 +279,7 @@ public final void drawText(int row, Canvas canvas, float x, float y, * @return the count of active rows. */ public int getActiveRows() { - return mActiveRows; + return mData.getActiveRows(); } /** @@ -420,11 +288,11 @@ public int getActiveRows() { * @return the count of active transcript rows. */ public int getActiveTranscriptRows() { - return mActiveTranscriptRows; + return mData.getActiveTranscriptRows(); } public String getTranscriptText() { - return internalGetTranscriptText(true, 0, -mActiveTranscriptRows, mColumns, mScreenRows); + return internalGetTranscriptText(true, 0, -mData.getActiveTranscriptRows(), mColumns, mScreenRows); } public String getSelectedText(int selX1, int selY1, int selX2, int selY2) { @@ -433,30 +301,18 @@ public String getSelectedText(int selX1, int selY1, int selX2, int selY2) { private String internalGetTranscriptText(boolean stripColors, int selX1, int selY1, int selX2, int selY2) { StringBuilder builder = new StringBuilder(); - char[] rowBuffer = mRowBuffer; - char[] data = mData; + UnicodeTranscript data = mData; int columns = mColumns; - if (selY1 < -mActiveTranscriptRows) { - selY1 = -mActiveTranscriptRows; + char[] line; + if (selY1 < -data.getActiveTranscriptRows()) { + selY1 = -data.getActiveTranscriptRows(); } if (selY2 >= mScreenRows) { selY2 = mScreenRows - 1; } for (int row = selY1; row <= selY2; row++) { - int offset = getOffset(row); - int lastPrintingChar = -1; - for (int column = 0; column < columns; column++) { - char c = data[offset + column]; - if (stripColors) { - c = (char) (c & 0xff); - } - if ((c & 0xff) != ' ') { - lastPrintingChar = column; - } - rowBuffer[column] = c; - } int x1 = 0; - int x2 = 0; + int x2; if ( row == selY1 ) { x1 = selX1; } @@ -465,10 +321,24 @@ private String internalGetTranscriptText(boolean stripColors, int selX1, int sel } else { x2 = columns; } - if (mLineWrap[externalToInternalRow(row)]) { - builder.append(rowBuffer, x1, x2 - x1); - } else { - builder.append(rowBuffer, x1, Math.max(0, Math.min(x2 - x1 + 1, lastPrintingChar + 1 - x1))); + line = data.getLine(row, x1, x2); + if (line == null) { + if (!data.getLineWrap(row)) { + builder.append('\n'); + } + continue; + } + int lastPrintingChar = -1; + int length = line.length; + for (int i = 0; i < length; i++) { + if (line[i] == 0) { + break; + } else if (line[i] != ' ') { + lastPrintingChar = i; + } + } + builder.append(line, 0, lastPrintingChar + 1); + if (!data.getLineWrap(row)) { builder.append('\n'); } } From 30e3995cd6ccc966b3784e29da534346e4d11cbe Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Fri, 30 Sep 2011 22:48:47 -0700 Subject: [PATCH 105/847] Support for Unicode text input and UTF-8 I/O to process * Ensure all output paths produce UTF-8 * Decode UTF-8 sequences in the input * Support switching to/from UTF-8 mode using ESC % escape codes * Add a preference to control whether the emulator is in UTF-8 mode by default Signed-off-by: Jack Palevich --- res/values/strings.xml | 3 + res/xml/preferences.xml | 6 + src/jackpal/androidterm/EmulatorView.java | 51 +++-- .../androidterm/session/TermSession.java | 37 +++- .../androidterm/session/TerminalEmulator.java | 184 +++++++++++++++++- .../androidterm/util/TermSettings.java | 11 ++ 6 files changed, 252 insertions(+), 40 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 84eda0284..a99a9eedc 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -56,6 +56,9 @@ Text + Enable UTF-8 mode by default + Whether UTF-8 mode is enabled by default. + Font size Choose character height in points. Font size diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml index 474ef2520..fba06fa57 100644 --- a/res/xml/preferences.xml +++ b/res/xml/preferences.xml @@ -73,6 +73,12 @@ android:entryValues="@array/entryvalues_color_preference" android:dialogTitle="@string/dialog_title_color_preference" /> + + = KEYCODE_OFFSET) { - handleKeyCode(result - KEYCODE_OFFSET, out, appMode); + handleKeyCode(result - KEYCODE_OFFSET, appMode); } else if (result >= 0) { - out.write(result); + mTermSession.write(result); } } - public boolean handleKeyCode(int keyCode, OutputStream out, boolean appMode) throws IOException { + public boolean handleKeyCode(int keyCode, boolean appMode) throws IOException { if (keyCode >= 0 && keyCode < mKeyCodes.length) { String code = null; if (appMode) { @@ -1966,10 +1964,7 @@ public boolean handleKeyCode(int keyCode, OutputStream out, boolean appMode) thr code = mKeyCodes[keyCode]; } if (code != null) { - int length = code.length(); - for (int i = 0; i < length; i++) { - out.write(code.charAt(i)); - } + mTermSession.write(code); return true; } } diff --git a/src/jackpal/androidterm/session/TermSession.java b/src/jackpal/androidterm/session/TermSession.java index c529fb18e..a649c148a 100644 --- a/src/jackpal/androidterm/session/TermSession.java +++ b/src/jackpal/androidterm/session/TermSession.java @@ -20,6 +20,11 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.CodingErrorAction; import java.util.ArrayList; import android.os.Handler; @@ -53,6 +58,10 @@ public class TermSession { private ByteQueue mByteQueue; private byte[] mReceiveBuffer; + private CharBuffer mWriteCharBuffer; + private ByteBuffer mWriteByteBuffer; + private CharsetEncoder mUTF8Encoder; + private static final int DEFAULT_COLUMNS = 80; private static final int DEFAULT_ROWS = 24; private static final String DEFAULT_SHELL = "/system/bin/sh -"; @@ -89,7 +98,7 @@ public TermSession(TermSettings settings, UpdateCallback notify, String initialC mTermIn = new FileInputStream(mTermFd); mTranscriptScreen = new TranscriptScreen(DEFAULT_COLUMNS, TRANSCRIPT_ROWS, DEFAULT_ROWS, 0, 7); - mEmulator = new TerminalEmulator(mTranscriptScreen, DEFAULT_COLUMNS, DEFAULT_ROWS, mTermOut); + mEmulator = new TerminalEmulator(settings, mTranscriptScreen, DEFAULT_COLUMNS, DEFAULT_ROWS, mTermOut); mIsRunning = true; @@ -105,6 +114,12 @@ public void run() { watcher.setName("Process watcher"); watcher.start(); + mWriteCharBuffer = CharBuffer.allocate(2); + mWriteByteBuffer = ByteBuffer.allocate(4); + mUTF8Encoder = Charset.forName("UTF-8").newEncoder(); + mUTF8Encoder.onMalformedInput(CodingErrorAction.REPLACE); + mUTF8Encoder.onUnmappableCharacter(CodingErrorAction.REPLACE); + mReceiveBuffer = new byte[4 * 1024]; mByteQueue = new ByteQueue(4 * 1024); @@ -146,7 +161,7 @@ private void sendInitialCommand(String initialCommand) { public void write(String data) { try { - mTermOut.write(data.getBytes()); + mTermOut.write(data.getBytes("UTF-8")); mTermOut.flush(); } catch (IOException e) { // Ignore exception @@ -155,6 +170,24 @@ public void write(String data) { } } + public void write(int codePoint) { + CharBuffer charBuf = mWriteCharBuffer; + ByteBuffer byteBuf = mWriteByteBuffer; + CharsetEncoder encoder = mUTF8Encoder; + try { + charBuf.clear(); + byteBuf.clear(); + Character.toChars(codePoint, charBuf.array(), 0); + encoder.reset(); + encoder.encode(charBuf, byteBuf, true); + encoder.flush(byteBuf); + mTermOut.write(byteBuf.array(), 0, byteBuf.position()-1); + mTermOut.flush(); + } catch (IOException e) { + // Ignore exception + } + } + private void createSubprocess(int[] processId) { String shell = mSettings.getShell(); if (shell == null || shell.equals("")) { diff --git a/src/jackpal/androidterm/session/TerminalEmulator.java b/src/jackpal/androidterm/session/TerminalEmulator.java index 3e0d19a0a..3b468c2f0 100644 --- a/src/jackpal/androidterm/session/TerminalEmulator.java +++ b/src/jackpal/androidterm/session/TerminalEmulator.java @@ -18,11 +18,18 @@ import java.io.FileOutputStream; import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CodingErrorAction; import android.util.Log; import jackpal.androidterm.TermDebug; import jackpal.androidterm.model.Screen; +import jackpal.androidterm.util.TermSettings; +import jackpal.androidterm.util.UnicodeTranscript; /** * Renders text into a screen. Contains all the terminal-specific knowlege and @@ -32,6 +39,7 @@ * video, color) alternate screen cursor key and keypad escape sequences. */ public class TerminalEmulator { + private TermSettings mTermSettings; /** * The cursor row. Numbered 0..mRows-1. @@ -118,6 +126,11 @@ public class TerminalEmulator { */ private static final int ESC_LEFT_SQUARE_BRACKET_QUESTION_MARK = 6; + /** + * Escape processing state: ESC % + */ + private static final int ESC_PERCENT = 7; + /** * True if the current escape sequence should continue, false if the current * escape sequence should be terminated. Used when parsing a single @@ -220,6 +233,13 @@ public class TerminalEmulator { */ private boolean mAboutToAutoWrap; + /** + * True if we just auto-wrapped and no character has been emitted on this + * line yet. Used to ensure combining characters following a character + * at the edge of the screen are stored in the proper place. + */ + private boolean mJustWrapped = false; + /** * Used for debugging, counts how many chars have been processed. */ @@ -246,6 +266,16 @@ public class TerminalEmulator { */ private int mScrollCounter = 0; + /** + * UTF-8 support + */ + private static final int UNICODE_REPLACEMENT_CHAR = 0xfffd; + private boolean mUTF8Mode = false; + private int mUTF8ToFollow = 0; + private ByteBuffer mUTF8ByteBuffer; + private CharBuffer mInputCharBuffer; + private CharsetDecoder mUTF8Decoder; + /** * Construct a terminal emulator that uses the supplied screen * @@ -254,13 +284,21 @@ public class TerminalEmulator { * @param rows the number of rows to emulate * @param termOut the output file descriptor that talks to the pseudo-tty. */ - public TerminalEmulator(Screen screen, int columns, int rows, - FileOutputStream termOut) { + public TerminalEmulator(TermSettings termSettings, + Screen screen, int columns, int rows, FileOutputStream termOut) { + mTermSettings = termSettings; mScreen = screen; mRows = rows; mColumns = columns; mTabStop = new boolean[mColumns]; mTermOut = termOut; + + mUTF8ByteBuffer = ByteBuffer.allocate(4); + mInputCharBuffer = CharBuffer.allocate(2); + mUTF8Decoder = Charset.forName("UTF-8").newDecoder(); + mUTF8Decoder.onMalformedInput(CodingErrorAction.REPLACE); + mUTF8Decoder.onUnmappableCharacter(CodingErrorAction.REPLACE); + reset(); } @@ -378,6 +416,11 @@ public void append(byte[] buffer, int base, int length) { } private void process(byte b) { + // Let the UTF-8 decoder try to handle it if we're in UTF-8 mode + if (mUTF8Mode && handleUTF8Sequence(b)) { + return; + } + switch (b) { case 0: // NUL // Do nothing @@ -465,6 +508,10 @@ private void process(byte b) { doEscLSBQuest(b); break; + case ESC_PERCENT: + doEscPercent(b); + break; + default: unknownSequence(b); break; @@ -476,6 +523,57 @@ private void process(byte b) { } } + private boolean handleUTF8Sequence(byte b) { + if (mUTF8ToFollow == 0 && ((b >> 7) & 0x1) == 0) { + // ASCII character -- we don't need to handle this + return false; + } + + if (mUTF8ToFollow > 0) { + if (((b >> 6) & 0x3) != 0x2) { + /* Not a UTF-8 continuation byte (doesn't begin with 0b10) + Replace the entire sequence with the replacement char */ + mUTF8ToFollow = 0; + mUTF8ByteBuffer.clear(); + emit(UNICODE_REPLACEMENT_CHAR); + return true; + } + + mUTF8ByteBuffer.put(b); + if (--mUTF8ToFollow == 0) { + // Sequence complete -- decode and emit it + ByteBuffer byteBuf = mUTF8ByteBuffer; + CharBuffer charBuf = mInputCharBuffer; + CharsetDecoder decoder = mUTF8Decoder; + + byteBuf.rewind(); + decoder.reset(); + decoder.decode(byteBuf, charBuf, true); + decoder.flush(charBuf); + emit(charBuf.array()); + + byteBuf.clear(); + charBuf.clear(); + } + } else { + if (((b >> 5) & 0x7) == 0x6) { // 0b110 -- two-byte sequence + mUTF8ToFollow = 1; + } else if (((b >> 4) & 0xf) == 0xe) { // 0b1110 -- three-byte sequence + mUTF8ToFollow = 2; + } else if (((b >> 3) & 0x1f) == 0x1e) { // 0b11110 -- four-byte sequence + mUTF8ToFollow = 3; + } else { + // Not a valid UTF-8 sequence start -- replace this char + emit(UNICODE_REPLACEMENT_CHAR); + return true; + } + + mUTF8ByteBuffer.put(b); + } + + return true; + } + private void setAltCharSet(boolean alternateCharSet) { mAlternateCharSet = alternateCharSet; } @@ -489,6 +587,19 @@ private int nextTabStop(int cursorCol) { return mColumns - 1; } + private void doEscPercent(byte b) { + switch (b) { + case '@': // Esc % @ -- return to ISO 2022 mode + mUTF8Mode = false; + break; + case 'G': // Esc % G -- UTF-8 mode + mUTF8Mode = true; + break; + default: // unimplemented character set + break; + } + } + private void doEscLSBQuest(byte b) { int mask = getDecFlagsMask(getArg0(0)); switch (b) { @@ -1142,17 +1253,19 @@ private boolean autoWrapEnabled() { } /** - * Send an ASCII character to the screen. + * Send a Unicode code point to the screen. * - * @param b the ASCII character to display. + * @param c The code point of the character to display */ - private void emit(byte b) { + private void emit(int c) { boolean autoWrap = autoWrapEnabled(); + int width = UnicodeTranscript.charWidth(c); if (autoWrap) { - if (mCursorCol == mColumns - 1 && mAboutToAutoWrap) { + if (mCursorCol == mColumns - 1 && (mAboutToAutoWrap || width == 2)) { mScreen.setLineWrap(mCursorRow); mCursorCol = 0; + mJustWrapped = true; if (mCursorRow + 1 < mBottomMargin) { mCursorRow++; } else { @@ -1161,21 +1274,67 @@ private void emit(byte b) { } } - if (mInsertMode) { // Move character to right one space - int destCol = mCursorCol + 1; + if (mInsertMode & width != 0) { // Move character to right one space + int destCol = mCursorCol + width; if (destCol < mColumns) { mScreen.blockCopy(mCursorCol, mCursorRow, mColumns - destCol, 1, destCol, mCursorRow); } } - mScreen.set(mCursorCol, mCursorRow, b, getForeColor(), getBackColor()); + if (width == 0) { + // Combining character -- store along with character it modifies + if (mJustWrapped) { + mScreen.set(mColumns - 1, mCursorRow - 1, c, getForeColor(), getBackColor()); + } else { + mScreen.set(mCursorCol - 1, mCursorRow, c, getForeColor(), getBackColor()); + } + } else { + mScreen.set(mCursorCol, mCursorRow, c, getForeColor(), getBackColor()); + mJustWrapped = false; + } if (autoWrap) { mAboutToAutoWrap = (mCursorCol == mColumns - 1); } - mCursorCol = Math.min(mCursorCol + 1, mColumns - 1); + mCursorCol = Math.min(mCursorCol + width, mColumns - 1); + } + + private void emit(byte b) { + emit((int) b); + } + + /** + * Send a UTF-16 char or surrogate pair to the screen. + * + * @param c A char[2] containing either a single UTF-16 char or a surrogate pair to be sent to the screen. + */ + private void emit(char[] c) { + if (Character.isHighSurrogate(c[0])) { + emit(Character.toCodePoint(c[0], c[1])); + } else { + emit((int) c[0]); + } + } + + /** + * Send an array of UTF-16 chars to the screen. + * + * @param c A char[] array whose contents are to be sent to the screen. + */ + private void emit(char[] c, int offset, int length) { + for (int i = offset; i < length; ++i) { + if (c[i] == 0) { + break; + } + if (Character.isHighSurrogate(c[i])) { + emit(Character.toCodePoint(c[i], c[i+1])); + ++i; + } else { + emit((int) c[i]); + } + } } private void setCursorRow(int row) { @@ -1228,6 +1387,11 @@ public void reset() { // mProcessedCharCount is preserved unchanged. setDefaultTabStops(); blockClear(0, 0, mColumns, mRows); + + mUTF8Mode = mTermSettings.defaultToUTF8Mode(); + mUTF8ToFollow = 0; + mUTF8ByteBuffer.clear(); + mInputCharBuffer.clear(); } public String getSelectedText(int x1, int y1, int x2, int y2) { diff --git a/src/jackpal/androidterm/util/TermSettings.java b/src/jackpal/androidterm/util/TermSettings.java index f366e7f99..2fc52c186 100644 --- a/src/jackpal/androidterm/util/TermSettings.java +++ b/src/jackpal/androidterm/util/TermSettings.java @@ -35,6 +35,7 @@ public class TermSettings { private int mUseCookedIME = 0; private String mShell; private String mInitialCommand; + private boolean mUTF8ByDefault = false; private static final String STATUSBAR_KEY = "statusbar"; private static final String CURSORSTYLE_KEY = "cursorstyle"; @@ -46,6 +47,7 @@ public class TermSettings { private static final String IME_KEY = "ime"; private static final String SHELL_KEY = "shell"; private static final String INITIALCOMMAND_KEY = "initialcommand"; + private static final String UTF8_KEY = "utf8_by_default"; public static final int WHITE = 0xffffffff; public static final int BLACK = 0xff000000; @@ -101,6 +103,7 @@ public void readPrefs(SharedPreferences prefs) { mUseCookedIME = readIntPref(IME_KEY, mUseCookedIME, 1); mShell = readStringPref(SHELL_KEY, mShell); mInitialCommand = readStringPref(INITIALCOMMAND_KEY, mInitialCommand); + mUTF8ByDefault = readBooleanPref(UTF8_KEY, false); mPrefs = null; // we leak a Context if we hold on to this } @@ -120,6 +123,10 @@ private String readStringPref(String key, String defaultValue) { return mPrefs.getString(key, defaultValue); } + private boolean readBooleanPref(String key, boolean defaultValue) { + return mPrefs.getBoolean(key, defaultValue); + } + public boolean showStatusBar() { return (mStatusBar != 0); } @@ -167,4 +174,8 @@ public String getShell() { public String getInitialCommand() { return mInitialCommand; } + + public boolean defaultToUTF8Mode() { + return mUTF8ByDefault; + } } From e09e02a704e5b2e447cc4d3543871894e503fe27 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sun, 9 Oct 2011 15:15:34 -0700 Subject: [PATCH 106/847] Restore SCREEN_CHECK_PERIOD constant. (I think Steven has a patch in his source tree that removes the need to use SCREEN_CHECK_PERIOD, but that patch wasn't accepted for the main branch, so SCREEN_CHECK_PERIOD is unused in Steven's tree, but is still required for the main branch.) --- src/jackpal/androidterm/EmulatorView.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/jackpal/androidterm/EmulatorView.java b/src/jackpal/androidterm/EmulatorView.java index 82d983ccc..ee8bb5a7c 100644 --- a/src/jackpal/androidterm/EmulatorView.java +++ b/src/jackpal/androidterm/EmulatorView.java @@ -155,6 +155,8 @@ public class EmulatorView extends View implements GestureDetector.OnGestureListe private static final int CURSOR_BLINK_PERIOD = 1000; + private static final int SCREEN_CHECK_PERIOD = 1000; + private boolean mCursorVisible = true; private boolean mIsSelectingText = false; From a347478487a6e1ca094b7070506bb0947a20f30a Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sun, 9 Oct 2011 17:08:44 -0700 Subject: [PATCH 107/847] Shorten UI message for "Default to UTF-8 mode". The original message text, while more accurate, did not fit in the UI. --- res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index a99a9eedc..0e1555aae 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -56,7 +56,7 @@ Text - Enable UTF-8 mode by default + Default to UTF-8 Whether UTF-8 mode is enabled by default. Font size From 86b70e1a9ae87db2b483e1461d48407e96aebf35 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sun, 9 Oct 2011 17:09:57 -0700 Subject: [PATCH 108/847] Micro optimization: avoid unnescessary shifts. --- src/jackpal/androidterm/session/TerminalEmulator.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/jackpal/androidterm/session/TerminalEmulator.java b/src/jackpal/androidterm/session/TerminalEmulator.java index 3b468c2f0..4703c8127 100644 --- a/src/jackpal/androidterm/session/TerminalEmulator.java +++ b/src/jackpal/androidterm/session/TerminalEmulator.java @@ -524,13 +524,13 @@ private void process(byte b) { } private boolean handleUTF8Sequence(byte b) { - if (mUTF8ToFollow == 0 && ((b >> 7) & 0x1) == 0) { + if (mUTF8ToFollow == 0 && (b & 0x80) == 0) { // ASCII character -- we don't need to handle this return false; } if (mUTF8ToFollow > 0) { - if (((b >> 6) & 0x3) != 0x2) { + if ((b & 0xc0) != 0x80) { /* Not a UTF-8 continuation byte (doesn't begin with 0b10) Replace the entire sequence with the replacement char */ mUTF8ToFollow = 0; @@ -556,11 +556,11 @@ private boolean handleUTF8Sequence(byte b) { charBuf.clear(); } } else { - if (((b >> 5) & 0x7) == 0x6) { // 0b110 -- two-byte sequence + if ((b & 0xe0) == 0xc0) { // 0b110 -- two-byte sequence mUTF8ToFollow = 1; - } else if (((b >> 4) & 0xf) == 0xe) { // 0b1110 -- three-byte sequence + } else if ((b & 0xf0) == 0xe0) { // 0b1110 -- three-byte sequence mUTF8ToFollow = 2; - } else if (((b >> 3) & 0x1f) == 0x1e) { // 0b11110 -- four-byte sequence + } else if ((b & 0xf8) == 0xf0) { // 0b11110 -- four-byte sequence mUTF8ToFollow = 3; } else { // Not a valid UTF-8 sequence start -- replace this char From 22ad15a2e72778cfa3fdf94a3b0c3ec0b5da16f9 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sun, 9 Oct 2011 17:14:21 -0700 Subject: [PATCH 109/847] Enable code to run pre-SDK-level 8. Introduce a utility class to deal with post-SDK-level-3 APIs. Cache the SDK level as an integer at start-up. Use reflection to access post-SDK-level-3 APIs. Add early out for space character, which shows up frequently. --- .../androidterm/util/PostAndroid3Utils.java | 65 +++++++++++++++++++ .../androidterm/util/UnicodeTranscript.java | 23 ++++--- 2 files changed, 78 insertions(+), 10 deletions(-) create mode 100644 src/jackpal/androidterm/util/PostAndroid3Utils.java diff --git a/src/jackpal/androidterm/util/PostAndroid3Utils.java b/src/jackpal/androidterm/util/PostAndroid3Utils.java new file mode 100644 index 000000000..080016cbf --- /dev/null +++ b/src/jackpal/androidterm/util/PostAndroid3Utils.java @@ -0,0 +1,65 @@ +package jackpal.androidterm.util; + +import java.lang.reflect.Method; + +import android.text.AndroidCharacter; +import android.util.Log; + +/** + * Provides APIs post Android version 3. + * + */ +public class PostAndroid3Utils { + private static String TAG = "PostAndroid3Utils"; + + public final static int SDK = + Integer.valueOf(android.os.Build.VERSION.SDK); + /** + * Definitions related to android.text.AndroidCharacter + */ + public static class AndroidCharacterComp { + public static final int EAST_ASIAN_WIDTH_NEUTRAL = 0; + public static final int EAST_ASIAN_WIDTH_AMBIGUOUS = 1; + public static final int EAST_ASIAN_WIDTH_HALF_WIDTH = 2; + public static final int EAST_ASIAN_WIDTH_FULL_WIDTH = 3; + public static final int EAST_ASIAN_WIDTH_NARROW = 4; + public static final int EAST_ASIAN_WIDTH_WIDE = 5; + + private static boolean mGetEastAsianWidthInitialized; + private static Method mGetEastAsianWidthMethod; + /** + * Calls AndroidCharacter.getEastAsianWidth if it exists, + * otherwise returns EAST_ASIAN_WIDTH_NARROW. + */ + public static int getEastAsianWidth(char c) { + int result = EAST_ASIAN_WIDTH_NARROW; + if (!mGetEastAsianWidthInitialized) { + mGetEastAsianWidthInitialized = true; + try { + Class[] parameterTypes = new Class[]{ + char.class}; + Method method = AndroidCharacter.class.getMethod( + "getEastAsianWidth", parameterTypes); + if (method.getGenericReturnType() != int.class) { + Log.e(TAG, "Unexpected return type for getEastAsianWidth"); + } else { + mGetEastAsianWidthMethod = method; + } + } catch (NoSuchMethodException e) { + // Pre Android API level 8 + } + } else { + if (mGetEastAsianWidthMethod != null) { + try { + Integer objectResult = (Integer) mGetEastAsianWidthMethod.invoke( + null, new Object[]{new Character(c)}); + result = objectResult.intValue(); + } catch(Exception e) { + Log.e(TAG, "Unexpected exception when calling getEastAsianWidth", e); + } + } + } + return result; + } + } +} diff --git a/src/jackpal/androidterm/util/UnicodeTranscript.java b/src/jackpal/androidterm/util/UnicodeTranscript.java index ce0ef1b45..c7c834f3a 100644 --- a/src/jackpal/androidterm/util/UnicodeTranscript.java +++ b/src/jackpal/androidterm/util/UnicodeTranscript.java @@ -16,7 +16,6 @@ package jackpal.androidterm.util; -import android.text.AndroidCharacter; import android.util.Log; /** @@ -360,6 +359,10 @@ public void blockSet(int sx, int sy, int w, int h, int val, * @return The display width of the Unicode code point. */ public static int charWidth(int codePoint) { + // Early out for spaces + if (codePoint == 32) { + return 1; + } switch (Character.getType(codePoint)) { case Character.CONTROL: case Character.FORMAT: @@ -368,7 +371,7 @@ public static int charWidth(int codePoint) { return 0; } - if (Integer.valueOf(android.os.Build.VERSION.SDK) < 8) { + if (PostAndroid3Utils.SDK < 8) { /* No East Asian wide char support when running on Android < 2.2, because getEastAsianWidth() was introduced in API 8 */ return 1; @@ -376,9 +379,9 @@ because getEastAsianWidth() was introduced in API 8 */ if (Character.charCount(codePoint) == 1) { // Android's getEastAsianWidth() only works for BMP characters - switch (AndroidCharacter.getEastAsianWidth((char) codePoint)) { - case AndroidCharacter.EAST_ASIAN_WIDTH_FULL_WIDTH: - case AndroidCharacter.EAST_ASIAN_WIDTH_WIDE: + switch (PostAndroid3Utils.AndroidCharacterComp.getEastAsianWidth((char) codePoint)) { + case PostAndroid3Utils.AndroidCharacterComp.EAST_ASIAN_WIDTH_FULL_WIDTH: + case PostAndroid3Utils.AndroidCharacterComp.EAST_ASIAN_WIDTH_WIDE: return 2; } } else { @@ -405,7 +408,7 @@ public static int charWidth(char cHigh, char cLow) { * character requested will be followed by a NUL; the contents of the rest * of the array could potentially be garbage. * - * @param row The row number to get (-mActiveTranscriptRows..mScreenRows-1) + * @param row The row number to get (-mActiveTranscriptRows..mScreenRows-1) * @param x1 The first screen position that's wanted * @param x2 One after the last screen position that's wanted * @return A char[] array containing the requested contents @@ -447,7 +450,7 @@ public char[] getLine(int row, int x1, int x2) { x2 = line.getSpaceUsed(); } int length = x2 - x1; - + if (tmpLine == null || tmpLine.length < length + 1) { tmpLine = new char[length+1]; } @@ -582,7 +585,7 @@ private FullUnicodeLine allocateFullLine(int row, int columns) { private byte[] allocateColor(int row, int columns) { byte[] color = new byte[columns]; - + // Set all of the columns to the default colors byte defaultColor = encodeColor(mDefaultForeColor, mDefaultBackColor); for (int i = 0; i < columns; ++i) { @@ -777,7 +780,7 @@ public void setChar(int column, int codePoint) { Character.toChars(codePoint, text, pos); } else { /* Store a combining character at the end of the existing contents, - so that it modifies them */ + so that it modifies them */ Character.toChars(codePoint, text, pos + oldLen); } @@ -805,7 +808,7 @@ public void setChar(int column, int codePoint) { // Array needs growing char[] newText = new char[text.length + columns]; System.arraycopy(text, 0, newText, 0, nextPos); - + System.arraycopy(text, nextPos, newText, nextPos + 1, spaceUsed - nextPos); mText = text = newText; } else { From dbf4f4ca02d6ac3dc86b79600bcaca9ecd9adf3f Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Fri, 21 Oct 2011 19:37:20 -0700 Subject: [PATCH 110/847] Update project organization for SDK version 14 --- ant.properties | 19 +++++++ build.properties | 2 - build.xml | 72 +++++++++++++----------- default.properties => project.properties | 6 +- 4 files changed, 61 insertions(+), 38 deletions(-) create mode 100644 ant.properties delete mode 100644 build.properties rename default.properties => project.properties (79%) diff --git a/ant.properties b/ant.properties new file mode 100644 index 000000000..f3714ddf9 --- /dev/null +++ b/ant.properties @@ -0,0 +1,19 @@ +# This file is used to override default values used by the Ant build system. +# +# This file must be checked in Version Control Systems, as it is +# integral to the build system of your project. + +# This file is only used by the Ant script. + +# You can use this to override default values such as +# 'source.dir' for the location of your java source folder and +# 'out.dir' for the location of your output folder. + +# You can also use it define how the release builds are signed by declaring +# the following properties: +# 'key.store' for the location of your keystore and +# 'key.alias' for the name of the key to use. +# The password will be asked during the build when you use the 'release' target. + +key.alias=jackpal.keystore +key.store=../../Documents/workspace/keystore/jackpal.keystore diff --git a/build.properties b/build.properties deleted file mode 100644 index 2799f2ac4..000000000 --- a/build.properties +++ /dev/null @@ -1,2 +0,0 @@ -key.store=../../Documents/workspace/keystore/jackpal.keystore -key.alias=jackpal.keystore diff --git a/build.xml b/build.xml index 6ad75ae77..71194c705 100644 --- a/build.xml +++ b/build.xml @@ -1,15 +1,14 @@ - - - - + + + - + - - + - - - + + - - + + diff --git a/default.properties b/project.properties similarity index 79% rename from default.properties rename to project.properties index 510b0908b..d79abae19 100644 --- a/default.properties +++ b/project.properties @@ -1,10 +1,10 @@ # This file is automatically generated by Android Tools. # Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# +# # This file must be checked in Version Control Systems. -# +# # To customize properties used by the Ant build system use, -# "build.properties", and override values to adapt the script to your +# "ant.properties", and override values to adapt the script to your # project structure. # Project target. From ba857043b110bb76d2cf37855b939ce9603796ec Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Fri, 21 Oct 2011 19:39:41 -0700 Subject: [PATCH 111/847] getEastAsianWidth: Actually invoke method after initializing when first called Signed-off-by: Jack Palevich --- .../androidterm/util/PostAndroid3Utils.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/jackpal/androidterm/util/PostAndroid3Utils.java b/src/jackpal/androidterm/util/PostAndroid3Utils.java index 080016cbf..0f28de23b 100644 --- a/src/jackpal/androidterm/util/PostAndroid3Utils.java +++ b/src/jackpal/androidterm/util/PostAndroid3Utils.java @@ -48,15 +48,15 @@ public static int getEastAsianWidth(char c) { } catch (NoSuchMethodException e) { // Pre Android API level 8 } - } else { - if (mGetEastAsianWidthMethod != null) { - try { - Integer objectResult = (Integer) mGetEastAsianWidthMethod.invoke( - null, new Object[]{new Character(c)}); - result = objectResult.intValue(); - } catch(Exception e) { - Log.e(TAG, "Unexpected exception when calling getEastAsianWidth", e); - } + } + + if (mGetEastAsianWidthMethod != null) { + try { + Integer objectResult = (Integer) mGetEastAsianWidthMethod.invoke( + null, new Object[]{new Character(c)}); + result = objectResult.intValue(); + } catch(Exception e) { + Log.e(TAG, "Unexpected exception when calling getEastAsianWidth", e); } } return result; From 5dc7ae591c876bcbd8454a3280b0f5a3d054691b Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Tue, 11 Oct 2011 03:04:06 -0700 Subject: [PATCH 112/847] Avoid going off end of offset array when going from width 2 to 1 at end of line Why this is happening is somewhat puzzling in the first place (since a width 2 character doesn't fit at the end of the line), but this prevents a crash. Signed-off-by: Jack Palevich --- src/jackpal/androidterm/util/UnicodeTranscript.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jackpal/androidterm/util/UnicodeTranscript.java b/src/jackpal/androidterm/util/UnicodeTranscript.java index c7c834f3a..21f96a09d 100644 --- a/src/jackpal/androidterm/util/UnicodeTranscript.java +++ b/src/jackpal/androidterm/util/UnicodeTranscript.java @@ -822,7 +822,7 @@ public void setChar(int column, int codePoint) { // Correct the offset for the next column to reflect width change if (column == 0) { offset[1] = (short) (newLen - 1); - } else { + } else if (column + 1 < columns) { offset[column + 1] = (short) (offset[column] + newLen - 1); } ++column; From 8c71cf58c020ed304c4d726e3c1d805411145833 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Tue, 11 Oct 2011 03:04:10 -0700 Subject: [PATCH 113/847] Fix getTranscriptText() behavior with autowrapped lines Since the Unicode patches, internalGetTranscriptText() has been ignoring trailing whitespace for lines that were autowrapped, which causes the content which was wrapped to the next line to be appended immediately after the last printing character -- not its original location. This patch fixes that regression. Signed-off-by: Jack Palevich --- src/jackpal/androidterm/session/TranscriptScreen.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/jackpal/androidterm/session/TranscriptScreen.java b/src/jackpal/androidterm/session/TranscriptScreen.java index f799046f1..8c22afe12 100644 --- a/src/jackpal/androidterm/session/TranscriptScreen.java +++ b/src/jackpal/androidterm/session/TranscriptScreen.java @@ -330,13 +330,18 @@ private String internalGetTranscriptText(boolean stripColors, int selX1, int sel } int lastPrintingChar = -1; int length = line.length; - for (int i = 0; i < length; i++) { + int i; + for (i = 0; i < length; i++) { if (line[i] == 0) { break; } else if (line[i] != ' ') { lastPrintingChar = i; } } + if (data.getLineWrap(row) && lastPrintingChar > -1 && x2 == columns) { + // If the line was wrapped, we shouldn't lose trailing space + lastPrintingChar = i - 1; + } builder.append(line, 0, lastPrintingChar + 1); if (!data.getLineWrap(row)) { builder.append('\n'); From d313f242ffa5d1ca083a7f36a3ce8c0f14a2c9f3 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Tue, 11 Oct 2011 03:04:15 -0700 Subject: [PATCH 114/847] Fix off-by-one error in getting selected text Signed-off-by: Jack Palevich --- src/jackpal/androidterm/session/TranscriptScreen.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/jackpal/androidterm/session/TranscriptScreen.java b/src/jackpal/androidterm/session/TranscriptScreen.java index 8c22afe12..6236c9d4f 100644 --- a/src/jackpal/androidterm/session/TranscriptScreen.java +++ b/src/jackpal/androidterm/session/TranscriptScreen.java @@ -317,7 +317,10 @@ private String internalGetTranscriptText(boolean stripColors, int selX1, int sel x1 = selX1; } if ( row == selY2 ) { - x2 = selX2; + x2 = selX2 + 1; + if (x2 > columns) { + x2 = columns; + } } else { x2 = columns; } From acdf3ac2aa4c979889e4bb296144838fe0f830ee Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Tue, 11 Oct 2011 03:04:33 -0700 Subject: [PATCH 115/847] Return early from charWidth() for all ASCII printable chars, not just space Signed-off-by: Jack Palevich --- src/jackpal/androidterm/util/UnicodeTranscript.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/jackpal/androidterm/util/UnicodeTranscript.java b/src/jackpal/androidterm/util/UnicodeTranscript.java index 21f96a09d..8217af35c 100644 --- a/src/jackpal/androidterm/util/UnicodeTranscript.java +++ b/src/jackpal/androidterm/util/UnicodeTranscript.java @@ -359,8 +359,8 @@ public void blockSet(int sx, int sy, int w, int h, int val, * @return The display width of the Unicode code point. */ public static int charWidth(int codePoint) { - // Early out for spaces - if (codePoint == 32) { + // Early out for ASCII printable characters + if (codePoint > 31 && codePoint < 127) { return 1; } switch (Character.getType(codePoint)) { From fcf0e2a0a1066b2ee4b5088064634d1455c728c8 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Tue, 11 Oct 2011 03:04:48 -0700 Subject: [PATCH 116/847] Hide the soft keyboard when pausing the activity Otherwise, the keyboard will remain open after the user leaves the activity, possibly covering up part of whatever the user is now doing. Signed-off-by: Jack Palevich --- src/jackpal/androidterm/Term.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index edea81bfa..c1b038d6d 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -267,6 +267,18 @@ public void onPause() { super.onPause(); mViewFlipper.pauseCurrentView(); + + /* Explicitly close the input method + Otherwise, the soft keyboard could cover up whatever activity takes + our place */ + final IBinder token = mViewFlipper.getWindowToken(); + new Thread() { + @Override + public void run() { + InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(token, 0); + } + }.start(); } @Override From 2d640ff2381925d9784248712671b7c47f2b7f56 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Tue, 11 Oct 2011 03:05:11 -0700 Subject: [PATCH 117/847] Return empty ExtractedText instead of null from getExtractedText() This silences the following error message in logs: E/InputMethodService(29043): Unexpected null in startExtractingText : mExtractedText = null, input connection = com.android.internal.view.InputConnectionWrapper@405ef968 Signed-off-by: Jack Palevich --- src/jackpal/androidterm/EmulatorView.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/jackpal/androidterm/EmulatorView.java b/src/jackpal/androidterm/EmulatorView.java index ee8bb5a7c..eff371308 100644 --- a/src/jackpal/androidterm/EmulatorView.java +++ b/src/jackpal/androidterm/EmulatorView.java @@ -285,6 +285,7 @@ public InputConnection onCreateInputConnection(EditorInfo outAttrs) { outAttrs.inputType = mUseCookedIme ? EditorInfo.TYPE_CLASS_TEXT : EditorInfo.TYPE_NULL; + outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI; return new InputConnection() { private boolean mInBatchEdit; /** @@ -388,7 +389,7 @@ public ExtractedText getExtractedText(ExtractedTextRequest arg0, if (TermDebug.LOG_IME) { Log.w(TAG, "getExtractedText" + arg0 + "," + arg1); } - return null; + return new ExtractedText(); } public CharSequence getTextAfterCursor(int n, int flags) { From 15a835f3449b80560be87c2f8c3637ef874dce1e Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Tue, 11 Oct 2011 03:05:20 -0700 Subject: [PATCH 118/847] Add a hack to remember the cursor position across screen size changes As it stands, the cursor ends up immediately after the last non-whitespace character in the transcript after a screen resize, which is very frequently incorrect (the last character in the output is often a space or newline, and it's possible the cursor has been moved, as during command line editing). Since lines could be re-wrapped during the screen size update, there's no easy way to calculate where the cursor should end up. Therefore, we use a hack: since ASCII ESC (\033) is non-printable and should never be part of the transcript, we mark the cursor location with an ESC before resizing, and restore the cursor to the location where the ESC ends up afterwards (taking care to save and restore the real character at the cursor position correctly). Signed-off-by: Jack Palevich --- .../androidterm/session/TerminalEmulator.java | 23 +++++++++++++++++++ .../androidterm/session/TranscriptScreen.java | 4 ++-- .../androidterm/util/UnicodeTranscript.java | 7 ++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/jackpal/androidterm/session/TerminalEmulator.java b/src/jackpal/androidterm/session/TerminalEmulator.java index 4703c8127..766826c03 100644 --- a/src/jackpal/androidterm/session/TerminalEmulator.java +++ b/src/jackpal/androidterm/session/TerminalEmulator.java @@ -313,6 +313,12 @@ public void updateSize(int columns, int rows) { if (rows <= 0) { throw new IllegalArgumentException("rows:" + rows); } + /* Save the character at the cursor (if one exists) and store an + * ASCII ESC character at the cursor's location + * This is an epic hack that lets us restore the cursor later... + */ + String charAtCursor = mScreen.getSelectedText(mCursorCol, mCursorRow, mCursorCol, mCursorRow); + mScreen.set(mCursorCol, mCursorRow, 27, 0, 0); String transcriptText = mScreen.getTranscriptText(); @@ -339,6 +345,8 @@ public void updateSize(int columns, int rows) { mCursorCol = 0; mAboutToAutoWrap = false; + int newCursorRow = -1; + int newCursorCol = -1; int end = transcriptText.length()-1; while ((end >= 0) && transcriptText.charAt(end) == '\n') { end--; @@ -352,10 +360,25 @@ public void updateSize(int columns, int rows) { } else if (c == '\n') { setCursorCol(0); doLinefeed(); + } else if (c == 27) { + /* We marked the cursor location with ESC earlier, so this + is the place to restore the cursor to */ + newCursorRow = mCursorRow; + newCursorCol = mCursorCol; + if (charAtCursor != null && charAtCursor.length() > 0) { + // Emit the real character that was in this spot + emit(charAtCursor.toCharArray(), 0, charAtCursor.length()); + } } else { emit(c); } } + + // If we marked a cursor location, move the cursor there now + if (newCursorRow != -1 && newCursorCol != -1) { + mCursorRow = newCursorRow; + mCursorCol = newCursorCol; + } } /** diff --git a/src/jackpal/androidterm/session/TranscriptScreen.java b/src/jackpal/androidterm/session/TranscriptScreen.java index 6236c9d4f..b64ede36e 100644 --- a/src/jackpal/androidterm/session/TranscriptScreen.java +++ b/src/jackpal/androidterm/session/TranscriptScreen.java @@ -326,7 +326,7 @@ private String internalGetTranscriptText(boolean stripColors, int selX1, int sel } line = data.getLine(row, x1, x2); if (line == null) { - if (!data.getLineWrap(row)) { + if (!data.getLineWrap(row) && row < selY2 && row < mScreenRows - 1) { builder.append('\n'); } continue; @@ -346,7 +346,7 @@ private String internalGetTranscriptText(boolean stripColors, int selX1, int sel lastPrintingChar = i - 1; } builder.append(line, 0, lastPrintingChar + 1); - if (!data.getLineWrap(row)) { + if (!data.getLineWrap(row) && row < selY2 && row < mScreenRows - 1) { builder.append('\n'); } } diff --git a/src/jackpal/androidterm/util/UnicodeTranscript.java b/src/jackpal/androidterm/util/UnicodeTranscript.java index 8217af35c..3ab4dba8b 100644 --- a/src/jackpal/androidterm/util/UnicodeTranscript.java +++ b/src/jackpal/androidterm/util/UnicodeTranscript.java @@ -363,6 +363,13 @@ public static int charWidth(int codePoint) { if (codePoint > 31 && codePoint < 127) { return 1; } + + /* HACK: We're using ASCII ESC to save the location of the cursor + across screen resizes, so we need to pretend that it has width 1 */ + if (codePoint == 27) { + return 1; + } + switch (Character.getType(codePoint)) { case Character.CONTROL: case Character.FORMAT: From 61e0e8b09aa6e8118bed630d2eb94f1b67affca5 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Tue, 11 Oct 2011 03:05:26 -0700 Subject: [PATCH 119/847] Preserve colors and formatting across screen size changes Currently, getTranscriptText() has no option for preserving color/formatting information, so this information is lost when resizing the screen. Fix this by adding a variant of getTranscriptText() which accepts a StringBuilder in which color information will be stored, and using it during resize operations. To save memory, this implementation only preserves color information for regions of the screen which contain text, so blank areas of the screen with colored backgrounds will still revert to the default background color. (Why StringBuilder? Post-Unicode, we cannot store information in the upper bits of a char, as we would have before. An ArrayList would be the most natural fit, but the need to box the values creates serious memory overhead. StringBuilder handles dynamic sizing of the array for us and uses less memory per cell than an ArrayList.) Signed-off-by: Jack Palevich --- src/jackpal/androidterm/model/Screen.java | 22 ++++++++++ .../androidterm/session/TerminalEmulator.java | 43 +++++++++++++------ .../androidterm/session/TranscriptScreen.java | 36 ++++++++++++++-- 3 files changed, 86 insertions(+), 15 deletions(-) diff --git a/src/jackpal/androidterm/model/Screen.java b/src/jackpal/androidterm/model/Screen.java index eddd7bd43..c7760bf93 100644 --- a/src/jackpal/androidterm/model/Screen.java +++ b/src/jackpal/androidterm/model/Screen.java @@ -99,6 +99,15 @@ void blockSet(int sx, int sy, int w, int h, int val, int foreColor, int */ String getTranscriptText(); + /** + * Get the contents of the transcript buffer as a text string with color + * information. + * + * @param colors A StringBuilder which will hold the colors. + * @return the contents of the transcript buffer. + */ + String getTranscriptText(StringBuilder colors); + /** * Get the selected text inside transcript buffer as a text string. * @param x1 Selection start @@ -109,6 +118,19 @@ void blockSet(int sx, int sy, int w, int h, int val, int foreColor, int */ String getSelectedText(int x1, int y1, int x2, int y2); + /** + * Get the selected text inside transcript buffer as a text string with + * color information. + * + * @param colors A StringBuilder which will hold the colors. + * @param x1 Selection start + * @param y1 Selection start + * @param x2 Selection end + * @param y2 Selection end + * @return the contents of the transcript buffer. + */ + String getSelectedText(StringBuilder colors, int x1, int y1, int x2, int y2); + /** * Resize the screen * @param columns diff --git a/src/jackpal/androidterm/session/TerminalEmulator.java b/src/jackpal/androidterm/session/TerminalEmulator.java index 766826c03..fe1c764d5 100644 --- a/src/jackpal/androidterm/session/TerminalEmulator.java +++ b/src/jackpal/androidterm/session/TerminalEmulator.java @@ -317,10 +317,12 @@ public void updateSize(int columns, int rows) { * ASCII ESC character at the cursor's location * This is an epic hack that lets us restore the cursor later... */ - String charAtCursor = mScreen.getSelectedText(mCursorCol, mCursorRow, mCursorCol, mCursorRow); + StringBuilder cursorColor = new StringBuilder(1); + String charAtCursor = mScreen.getSelectedText(cursorColor, mCursorCol, mCursorRow, mCursorCol, mCursorRow); mScreen.set(mCursorCol, mCursorRow, 27, 0, 0); - String transcriptText = mScreen.getTranscriptText(); + StringBuilder colors = new StringBuilder(); + String transcriptText = mScreen.getTranscriptText(colors); mScreen.resize(columns, rows, mForeColor, mBackColor); @@ -352,11 +354,16 @@ public void updateSize(int columns, int rows) { end--; } char c, cLow; + int foreColor, backColor; + int colorOffset = 0; for(int i = 0; i <= end; i++) { c = transcriptText.charAt(i); + foreColor = (colors.charAt(i-colorOffset) >> 4) & 0xf; + backColor = colors.charAt(i-colorOffset) & 0xf; if (Character.isHighSurrogate(c)) { cLow = transcriptText.charAt(++i); - emit(Character.toCodePoint(c, cLow)); + emit(Character.toCodePoint(c, cLow), foreColor, backColor); + ++colorOffset; } else if (c == '\n') { setCursorCol(0); doLinefeed(); @@ -367,10 +374,12 @@ public void updateSize(int columns, int rows) { newCursorCol = mCursorCol; if (charAtCursor != null && charAtCursor.length() > 0) { // Emit the real character that was in this spot - emit(charAtCursor.toCharArray(), 0, charAtCursor.length()); + foreColor = (cursorColor.charAt(0) >> 4) & 0xf; + backColor = cursorColor.charAt(0) & 0xf; + emit(charAtCursor.toCharArray(), 0, charAtCursor.length(), foreColor, backColor); } } else { - emit(c); + emit(c, foreColor, backColor); } } @@ -1279,8 +1288,10 @@ private boolean autoWrapEnabled() { * Send a Unicode code point to the screen. * * @param c The code point of the character to display + * @param foreColor The foreground color of the character + * @param backColor The background color of the character */ - private void emit(int c) { + private void emit(int c, int foreColor, int backColor) { boolean autoWrap = autoWrapEnabled(); int width = UnicodeTranscript.charWidth(c); @@ -1308,12 +1319,12 @@ private void emit(int c) { if (width == 0) { // Combining character -- store along with character it modifies if (mJustWrapped) { - mScreen.set(mColumns - 1, mCursorRow - 1, c, getForeColor(), getBackColor()); + mScreen.set(mColumns - 1, mCursorRow - 1, c, foreColor, backColor); } else { - mScreen.set(mCursorCol - 1, mCursorRow, c, getForeColor(), getBackColor()); + mScreen.set(mCursorCol - 1, mCursorRow, c, foreColor, backColor); } } else { - mScreen.set(mCursorCol, mCursorRow, c, getForeColor(), getBackColor()); + mScreen.set(mCursorCol, mCursorRow, c, foreColor, backColor); mJustWrapped = false; } @@ -1324,6 +1335,10 @@ private void emit(int c) { mCursorCol = Math.min(mCursorCol + width, mColumns - 1); } + private void emit(int c) { + emit(c, getForeColor(), getBackColor()); + } + private void emit(byte b) { emit((int) b); } @@ -1346,20 +1361,24 @@ private void emit(char[] c) { * * @param c A char[] array whose contents are to be sent to the screen. */ - private void emit(char[] c, int offset, int length) { + private void emit(char[] c, int offset, int length, int foreColor, int backColor) { for (int i = offset; i < length; ++i) { if (c[i] == 0) { break; } if (Character.isHighSurrogate(c[i])) { - emit(Character.toCodePoint(c[i], c[i+1])); + emit(Character.toCodePoint(c[i], c[i+1]), foreColor, backColor); ++i; } else { - emit((int) c[i]); + emit((int) c[i], foreColor, backColor); } } } + private void emit(char[] c, int offset, int length) { + emit(c, offset, length, getForeColor(), getBackColor()); + } + private void setCursorRow(int row) { mCursorRow = row; mAboutToAutoWrap = false; diff --git a/src/jackpal/androidterm/session/TranscriptScreen.java b/src/jackpal/androidterm/session/TranscriptScreen.java index b64ede36e..0aa36a5f8 100644 --- a/src/jackpal/androidterm/session/TranscriptScreen.java +++ b/src/jackpal/androidterm/session/TranscriptScreen.java @@ -292,18 +292,27 @@ public int getActiveTranscriptRows() { } public String getTranscriptText() { - return internalGetTranscriptText(true, 0, -mData.getActiveTranscriptRows(), mColumns, mScreenRows); + return internalGetTranscriptText(null, 0, -mData.getActiveTranscriptRows(), mColumns, mScreenRows); + } + + public String getTranscriptText(StringBuilder colors) { + return internalGetTranscriptText(colors, 0, -mData.getActiveTranscriptRows(), mColumns, mScreenRows); } public String getSelectedText(int selX1, int selY1, int selX2, int selY2) { - return internalGetTranscriptText(true, selX1, selY1, selX2, selY2); + return internalGetTranscriptText(null, selX1, selY1, selX2, selY2); + } + + public String getSelectedText(StringBuilder colors, int selX1, int selY1, int selX2, int selY2) { + return internalGetTranscriptText(colors, selX1, selY1, selX2, selY2); } - private String internalGetTranscriptText(boolean stripColors, int selX1, int selY1, int selX2, int selY2) { + private String internalGetTranscriptText(StringBuilder colors, int selX1, int selY1, int selX2, int selY2) { StringBuilder builder = new StringBuilder(); UnicodeTranscript data = mData; int columns = mColumns; char[] line; + byte[] rowColorBuffer = null; if (selY1 < -data.getActiveTranscriptRows()) { selY1 = -data.getActiveTranscriptRows(); } @@ -325,9 +334,15 @@ private String internalGetTranscriptText(boolean stripColors, int selX1, int sel x2 = columns; } line = data.getLine(row, x1, x2); + if (colors != null) { + rowColorBuffer = data.getLineColor(row, x1, x2); + } if (line == null) { if (!data.getLineWrap(row) && row < selY2 && row < mScreenRows - 1) { builder.append('\n'); + if (colors != null) { + colors.append((char) 0); + } } continue; } @@ -346,8 +361,23 @@ private String internalGetTranscriptText(boolean stripColors, int selX1, int sel lastPrintingChar = i - 1; } builder.append(line, 0, lastPrintingChar + 1); + if (colors != null) { + int column = 0; + for (int j = 0; j < lastPrintingChar + 1; ++j) { + colors.append((char) rowColorBuffer[column]); + if (Character.isHighSurrogate(line[j])) { + column += UnicodeTranscript.charWidth(Character.toCodePoint(line[j], line[j+1])); + ++j; + } else { + column += UnicodeTranscript.charWidth(line[j]); + } + } + } if (!data.getLineWrap(row) && row < selY2 && row < mScreenRows - 1) { builder.append('\n'); + if (colors != null) { + colors.append((char) 0); + } } } return builder.toString(); From 6f3f98aaea5aa714f88080a27483d9f62b8a57c2 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Tue, 11 Oct 2011 03:20:08 -0700 Subject: [PATCH 120/847] Make the screen a movable window in the transcript buffer Instead of the current scheme of having 0..mScreenRows-1 be the screen, and everything else be the transcript, let the screen be a movable window into the circular buffer. This allows scrolling the whole screen to be much faster (just move the window), and should make fast resize easier. Signed-off-by: Jack Palevich --- .../androidterm/util/UnicodeTranscript.java | 128 +++++++++++++----- 1 file changed, 96 insertions(+), 32 deletions(-) diff --git a/src/jackpal/androidterm/util/UnicodeTranscript.java b/src/jackpal/androidterm/util/UnicodeTranscript.java index 3ab4dba8b..a8da4e010 100644 --- a/src/jackpal/androidterm/util/UnicodeTranscript.java +++ b/src/jackpal/androidterm/util/UnicodeTranscript.java @@ -51,10 +51,11 @@ public class UnicodeTranscript { private int mScreenRows; private int mColumns; private int mActiveTranscriptRows = 0; - private int mNextTranscriptRow = 0; private int mDefaultForeColor = 0; private int mDefaultBackColor = 0; + private int mScreenFirstRow = 0; + private char[] tmpChar = new char[2]; private char[] tmpLine; private byte[] tmpColor; @@ -99,9 +100,10 @@ public int getActiveRows() { * External coordinate system: * -mActiveTranscriptRows to mScreenRows-1, with the screen being * 0..mScreenRows-1 - * Internal coordinate system: 0..mScreenRows-1 is the active screen, - * whereas mScreenRows..mActiveTranscriptRows is the transcript (as a - * circular buffer). + * Internal coordinate system: the mScreenRows lines starting at + * mScreenFirstRow comprise the screen, while the mActiveTranscriptRows + * lines ending at mScreenRows-1 form the transcript (as a circular + * buffer). * * @param extRow a row in the external coordinate system. * @return The row corresponding to the input argument in the private @@ -116,12 +118,12 @@ private int externalToInternalRow(int extRow) { } if (extRow >= 0) { - return extRow; + return (mScreenFirstRow + extRow) % mTotalRows; } else { - if (mNextTranscriptRow >= -extRow) { - return mScreenRows + mNextTranscriptRow + extRow; + if (-extRow > mScreenFirstRow) { + return mTotalRows + mScreenFirstRow + extRow; } else { - return mScreenRows + mActiveTranscriptRows + mNextTranscriptRow + extRow; + return mScreenFirstRow + extRow; } } } @@ -134,6 +136,49 @@ public boolean getLineWrap(int row) { return mLineWrap[externalToInternalRow(row)]; } + /** + * Block copy lines and associated metadata from one location to another + * in the circular buffer, taking wraparound into account. + * + * @param src The first line to be copied. + * @param len The number of lines to be copied. + * @param shift The offset of the destination from the source. + */ + private void blockCopyLines(int src, int len, int shift) { + int totalRows = mTotalRows; + + int dst; + if (src + shift >= 0) { + dst = (src + shift) % totalRows; + } else { + dst = totalRows + src + shift; + } + + if (src + len <= totalRows && dst + len <= totalRows) { + // Fast path -- no wraparound + System.arraycopy(mLines, src, mLines, dst, len); + System.arraycopy(mColor, src, mColor, dst, len); + System.arraycopy(mLineWrap, src, mLineWrap, dst, len); + return; + } + + if (shift < 0) { + // Do the copy from top to bottom + for (int i = 0; i < len; ++i) { + mLines[(dst + i) % totalRows] = mLines[(src + i) % totalRows]; + mColor[(dst + i) % totalRows] = mColor[(src + i) % totalRows]; + mLineWrap[(dst + i) % totalRows] = mLineWrap[(src + i) % totalRows]; + } + } else { + // Do the copy from bottom to top + for (int i = len - 1; i >= 0; --i) { + mLines[(dst + i) % totalRows] = mLines[(src + i) % totalRows]; + mColor[(dst + i) % totalRows] = mColor[(src + i) % totalRows]; + mLineWrap[(dst + i) % totalRows] = mLineWrap[(src + i) % totalRows]; + } + } + } + /** * Scroll the screen down one line. To scroll the whole screen of a 24 line * screen, the arguments would be (0, 24). @@ -158,37 +203,56 @@ public void scroll(int topMargin, int bottomMargin) { int screenRows = mScreenRows; int totalRows = mTotalRows; - int nextTranscriptRow = mNextTranscriptRow; - --bottomMargin; - /* Save the scrolled line, move the lines below it up one line, then - insert the scrolled line into the transcript */ + if (topMargin == 0 && bottomMargin == screenRows) { + // Fast path -- scroll the entire screen + mScreenFirstRow = (mScreenFirstRow + 1) % totalRows; + if (mActiveTranscriptRows < totalRows - screenRows) { + ++mActiveTranscriptRows; + } + + // Blank the bottom margin + int blankRow = externalToInternalRow(bottomMargin - 1); + mLines[blankRow] = null; + mColor[blankRow] = null; + mLineWrap[blankRow] = false; + + return; + } + + int screenFirstRow = mScreenFirstRow; + int scrollLen = bottomMargin - topMargin; + int topMarginInt = externalToInternalRow(topMargin); + int bottomMarginInt = externalToInternalRow(bottomMargin); + + /* Save the scrolled line, move the lines above it on the screen down + one line, move the lines on screen below the bottom margin down + one line, then insert the scrolled line into the transcript */ Object[] lines = mLines; byte[][] color = mColor; boolean[] lineWrap = mLineWrap; - Object scrollLine = lines[topMargin]; - byte[] scrollColor = color[topMargin]; - boolean scrollLineWrap = lineWrap[topMargin]; - System.arraycopy(lines, topMargin + 1, lines, topMargin, bottomMargin - topMargin); - System.arraycopy(color, topMargin + 1, color, topMargin, bottomMargin - topMargin); - System.arraycopy(lineWrap, topMargin + 1, lineWrap, topMargin, bottomMargin - topMargin); - lines[screenRows + nextTranscriptRow] = scrollLine; - color[screenRows + nextTranscriptRow] = scrollColor; - lineWrap[screenRows + nextTranscriptRow] = scrollLineWrap; - ++nextTranscriptRow; - if (screenRows + nextTranscriptRow >= totalRows) { - // Wrap around the circular buffer - nextTranscriptRow = 0; - } - mNextTranscriptRow = nextTranscriptRow; - if (mActiveTranscriptRows + screenRows < totalRows) { + Object scrollLine = lines[topMarginInt]; + byte[] scrollColor = color[topMarginInt]; + boolean scrollLineWrap = lineWrap[topMarginInt]; + blockCopyLines(screenFirstRow, topMargin, 1); + blockCopyLines(bottomMarginInt, screenRows - bottomMargin, 1); + lines[screenFirstRow] = scrollLine; + color[screenFirstRow] = scrollColor; + lineWrap[screenFirstRow] = scrollLineWrap; + + // Update the screen location + mScreenFirstRow = (screenFirstRow + 1) % totalRows; + if (mActiveTranscriptRows < totalRows - screenRows) { ++mActiveTranscriptRows; } - // Make sure the new last line is blank - lines[bottomMargin] = null; - color[bottomMargin] = null; - lineWrap[bottomMargin] = false; + // Blank the bottom margin + int blankRow = externalToInternalRow(bottomMargin - 1); + lines[blankRow] = null; + color[blankRow] = null; + lineWrap[blankRow] = false; + + return; } /** From 06013b2021be9a69e204d7444d1f639b395d4395 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Tue, 11 Oct 2011 03:20:14 -0700 Subject: [PATCH 121/847] Implement fast resize for cases where width remains constant A resize operation currently requires getting the entire transcript text, throwing away the backing store, constructing a new one, and then writing the transcript to the new store. Particularly when the transcript is large, this is a slow and inefficient operation. As it turns out, it's really quite easy to change the number of screen rows for a particular backing store, which allows much faster resize operations in the case where the number of columns does not change. This is particularly useful when using soft keyboards, where the number of rows will change depending on whether the keyboard is displayed, but the number of columns doesn't. Signed-off-by: Jack Palevich --- src/jackpal/androidterm/model/Screen.java | 13 +++ .../androidterm/session/TerminalEmulator.java | 46 +++++--- .../androidterm/session/TranscriptScreen.java | 10 ++ .../androidterm/util/UnicodeTranscript.java | 103 ++++++++++++++++++ 4 files changed, 157 insertions(+), 15 deletions(-) diff --git a/src/jackpal/androidterm/model/Screen.java b/src/jackpal/androidterm/model/Screen.java index c7760bf93..c280e0180 100644 --- a/src/jackpal/androidterm/model/Screen.java +++ b/src/jackpal/androidterm/model/Screen.java @@ -131,6 +131,19 @@ void blockSet(int sx, int sy, int w, int h, int val, int foreColor, int */ String getSelectedText(StringBuilder colors, int x1, int y1, int x2, int y2); + /** + * Try to resize the screen without losing its contents. + * + * @param columns + * @param rows + * @param cursor An int[2] containing the current cursor position + * { col, row }. If the resize succeeds, the array will be + * updated to reflect the new location. + * @return Whether the resize succeeded. If the operation fails, save the + * contents of the screen and then use the standard resize. + */ + boolean fastResize(int columns, int rows, int[] cursor); + /** * Resize the screen * @param columns diff --git a/src/jackpal/androidterm/session/TerminalEmulator.java b/src/jackpal/androidterm/session/TerminalEmulator.java index fe1c764d5..2167a2aa4 100644 --- a/src/jackpal/androidterm/session/TerminalEmulator.java +++ b/src/jackpal/androidterm/session/TerminalEmulator.java @@ -313,18 +313,29 @@ public void updateSize(int columns, int rows) { if (rows <= 0) { throw new IllegalArgumentException("rows:" + rows); } - /* Save the character at the cursor (if one exists) and store an - * ASCII ESC character at the cursor's location - * This is an epic hack that lets us restore the cursor later... - */ - StringBuilder cursorColor = new StringBuilder(1); - String charAtCursor = mScreen.getSelectedText(cursorColor, mCursorCol, mCursorRow, mCursorCol, mCursorRow); - mScreen.set(mCursorCol, mCursorRow, 27, 0, 0); - StringBuilder colors = new StringBuilder(); - String transcriptText = mScreen.getTranscriptText(colors); - - mScreen.resize(columns, rows, mForeColor, mBackColor); + // Try to resize the screen without getting the transcript + int[] cursor = { mCursorCol, mCursorRow }; + boolean fastResize = mScreen.fastResize(columns, rows, cursor); + + StringBuilder cursorColor = null; + String charAtCursor = null; + StringBuilder colors = null; + String transcriptText = null; + if (!fastResize) { + /* Save the character at the cursor (if one exists) and store an + * ASCII ESC character at the cursor's location + * This is an epic hack that lets us restore the cursor later... + */ + cursorColor = new StringBuilder(1); + charAtCursor = mScreen.getSelectedText(cursorColor, mCursorCol, mCursorRow, mCursorCol, mCursorRow); + mScreen.set(mCursorCol, mCursorRow, 27, 0, 0); + + colors = new StringBuilder(); + transcriptText = mScreen.getTranscriptText(colors); + + mScreen.resize(columns, rows, mForeColor, mBackColor); + } if (mRows != rows) { mRows = rows; @@ -338,11 +349,16 @@ public void updateSize(int columns, int rows) { mTabStop = new boolean[mColumns]; int toTransfer = Math.min(oldColumns, columns); System.arraycopy(oldTabStop, 0, mTabStop, 0, toTransfer); - while (mCursorCol >= columns) { - mCursorCol -= columns; - mCursorRow = Math.min(mBottomMargin-1, mCursorRow + 1); - } } + + if (fastResize) { + // Only need to make sure the cursor is in the right spot + mCursorCol = cursor[0]; + mCursorRow = cursor[1]; + + return; + } + mCursorRow = 0; mCursorCol = 0; mAboutToAutoWrap = false; diff --git a/src/jackpal/androidterm/session/TranscriptScreen.java b/src/jackpal/androidterm/session/TranscriptScreen.java index 0aa36a5f8..1d3cb600a 100644 --- a/src/jackpal/androidterm/session/TranscriptScreen.java +++ b/src/jackpal/androidterm/session/TranscriptScreen.java @@ -383,6 +383,16 @@ private String internalGetTranscriptText(StringBuilder colors, int selX1, int se return builder.toString(); } + public boolean fastResize(int columns, int rows, int[] cursor) { + if (mData.resize(columns, rows, cursor)) { + mColumns = columns; + mScreenRows = rows; + return true; + } else { + return false; + } + } + public void resize(int columns, int rows, int foreColor, int backColor) { init(columns, mTotalRows, rows, foreColor, backColor); } diff --git a/src/jackpal/androidterm/util/UnicodeTranscript.java b/src/jackpal/androidterm/util/UnicodeTranscript.java index a8da4e010..120f83431 100644 --- a/src/jackpal/androidterm/util/UnicodeTranscript.java +++ b/src/jackpal/androidterm/util/UnicodeTranscript.java @@ -136,6 +136,109 @@ public boolean getLineWrap(int row) { return mLineWrap[externalToInternalRow(row)]; } + /** + * Resize the screen which this transcript backs. Currently, this + * only works if the number of columns does not change. + * + * @param newColumns The number of columns the screen should have. + * @param newRows The number of rows the screen should have. + * @param cursor An int[2] containing the cursor location. + * @return Whether or not the resize succeeded. If the resize failed, + * the caller may "resize" the screen by copying out all the data + * and placing it into a new transcript of the correct size. + */ + public boolean resize(int newColumns, int newRows, int[] cursor) { + if (newColumns != mColumns || newRows > mTotalRows) { + return false; + } + + int screenRows = mScreenRows; + int activeTranscriptRows = mActiveTranscriptRows; + int shift = screenRows - newRows; + if (shift < -activeTranscriptRows) { + // We want to add blank lines at the bottom instead of at the top + Object[] lines = mLines; + byte[][] color = mColor; + boolean[] lineWrap = mLineWrap; + int screenFirstRow = mScreenFirstRow; + int totalRows = mTotalRows; + for (int i = 0; i < activeTranscriptRows - shift; ++i) { + int index = (screenFirstRow + screenRows + i) % totalRows; + lines[index] = null; + color[index] = null; + lineWrap[index] = false; + } + shift = -activeTranscriptRows; + } else if (shift > 0 && cursor[1] != screenRows - 1) { + /* When shrinking the screen, we want to hide blank lines at the + bottom in preference to lines at the top of the screen */ + Object[] lines = mLines; + for (int i = screenRows - 1; i >= 0; --i) { + int index = externalToInternalRow(i); + if (lines[index] == null) { + // Line is blank + --shift; + if (shift == 0) { + break; + } else { + continue; + } + } + + char[] line; + if (lines[index] instanceof char[]) { + line = (char[]) lines[index]; + } else { + line = ((FullUnicodeLine) lines[index]).getLine(); + } + + int len = line.length; + int j; + for (j = 0; j < len; ++j) { + if (line[j] == 0) { + // We've reached the end of the line + j = len; + break; + } else if (line[j] != ' ') { + // Line is not blank + break; + } + } + + if (j == len) { + // Line is blank + --shift; + if (shift == 0) { + break; + } else { + continue; + } + } else { + // Line not blank -- we keep it and everything above + break; + } + } + } + + if (shift > 0 || (shift < 0 && mScreenFirstRow >= -shift)) { + // All we're doing is moving the top of the screen. + mScreenFirstRow = (mScreenFirstRow + shift) % mTotalRows; + } else if (shift < 0) { + // The new top of the screen wraps around the top of the array. + mScreenFirstRow = mTotalRows + mScreenFirstRow + shift; + } + + if (mActiveTranscriptRows + shift < 0) { + mActiveTranscriptRows = 0; + } else { + mActiveTranscriptRows += shift; + } + cursor[1] -= shift; + mScreenRows = newRows; + + return true; + } + /** * Block copy lines and associated metadata from one location to another * in the circular buffer, taking wraparound into account. From 99908d5c8de5372b7b7db0e520f54980abc00f37 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Fri, 21 Oct 2011 20:01:22 -0700 Subject: [PATCH 122/847] Increment version number. --- AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index d783bad43..17b28447e 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,6 +1,6 @@ From 36ba6921517a192dc539471d7337e2df0849339c Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Fri, 21 Oct 2011 20:06:41 -0700 Subject: [PATCH 123/847] Move atari font notice out of res directory. --- {res/drawable => docs}/atari_small_notice.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {res/drawable => docs}/atari_small_notice.txt (100%) diff --git a/res/drawable/atari_small_notice.txt b/docs/atari_small_notice.txt similarity index 100% rename from res/drawable/atari_small_notice.txt rename to docs/atari_small_notice.txt From 49537fc4b194951f8d69c2c3b2e46327501afc00 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Fri, 21 Oct 2011 20:21:57 -0700 Subject: [PATCH 124/847] Remove executable bit. These files are not executable, and should not have executable bits set. These bits sometimes gets set when importing files from Windows. --- res/drawable-hdpi/app_terminal.png | Bin res/drawable-hdpi/atari_small.png | Bin res/values-tr/arrays.xml | 0 res/values-tr/strings.xml | 0 4 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 res/drawable-hdpi/app_terminal.png mode change 100755 => 100644 res/drawable-hdpi/atari_small.png mode change 100755 => 100644 res/values-tr/arrays.xml mode change 100755 => 100644 res/values-tr/strings.xml diff --git a/res/drawable-hdpi/app_terminal.png b/res/drawable-hdpi/app_terminal.png old mode 100755 new mode 100644 diff --git a/res/drawable-hdpi/atari_small.png b/res/drawable-hdpi/atari_small.png old mode 100755 new mode 100644 diff --git a/res/values-tr/arrays.xml b/res/values-tr/arrays.xml old mode 100755 new mode 100644 diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml old mode 100755 new mode 100644 From 2496296e471d4f58fd51525f57cb75b92ff32ea7 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Fri, 21 Oct 2011 20:58:24 -0700 Subject: [PATCH 125/847] Create a status bar notification icon. See http://developer.android.com/guide/practices/ui_guidelines/icon_design_status_bar.html --- docs/notification icon source.png | Bin 0 -> 1242 bytes res/drawable/service_notification_icon.png | Bin 0 -> 633 bytes src/jackpal/androidterm/TermService.java | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 docs/notification icon source.png create mode 100644 res/drawable/service_notification_icon.png diff --git a/docs/notification icon source.png b/docs/notification icon source.png new file mode 100644 index 0000000000000000000000000000000000000000..f120de808e8b072b5a4dba5bdb657fe2e6895d98 GIT binary patch literal 1242 zcmV<01SR{4P)*ad;@P9osvuN~r3FQ3Ay|4S z9z0h3Poy4uC?3R~ycG4&)*nF&+Ng-+*q%xY1*LdU1U1_CRw6blJ71?W#oc{EVlwmQ zyKg@G&1BZ#OXB?eT*;3+0!eq$qRa#Yuwx$xU;*ryfMa=auw%gKO8up!rH9F6@)hka zdZ;f`*)PwpCGeYCKPM82sovh+nX3_r@-}T@Vc`yeeO*^qH{8|Lbu~sFg9HkNf;u=j zD3nU2Twh<`cW)R2URhbmkaNFM18<7u_KysR<&VY1#e1H4`HmXi3s}3xBBps}o6P6) zJvB8ogPxt`&Kdz)Y9A~wFApY(@{8{7Zfn9KbU~oAvy&eT@ir}c(Ioc{2;Apw8nhMT zcgX$yeRXL8EsNT$QlWKc=TOA%A>cS)4-_X#Y zve~Sfo10Su0|VN*`YtpoM{I6xs_E%zRb5@J*4EZ4yJPu$Y;25oUTSM=%Yz*RL3^pp z%R;eORO930K{~POo0ynTJ3BkxlBt`x7BF3vsN>^folflf`Mv1u>`XgvEnrrlYizAh z+yYQ05a77MvpHV@Y)I3pgHbWVTthhwWdb=E6$>RP$4LudL%^u%Ai#z>M0%K2z^GWH ziB?RGKK-d^x&A2>C4l|#P$mM{&kky_3K%+K%z~@}M#W;r7@97$M4$^gbi{}Rp-dpa z;$XikP$mM{&kkx4zz`<$AXy^J*+C=4!4M|%Agh4UmxFn_;TvfcFe(;4Oy(J*OrTwa zc^c6jDS#cHTXfkJ2OFLUs}4rR408?TFq8@8VD#k>N>YYNTLp}Y8Riaxly_l*6n7M#Vx&$}nk^i7XKU z*pPtZVOAaNIM-0hTLmm{a()k>Ok@rAvx8c!Iv5p$GK*P|IN0!nqoxzU0@#p%<6%}E z>^Rp*%gYjBczUflIF(9y|7mZSM`VX_-3VkataWf>W20(rZdM~BBaww=GO?DH7Oyae zZmhOmM0lv=KwNmQ))2q~*f9ae^5WqC0J!A%7?G6!@arQqFLv-Q6dCa#blX+N@Z|2} z^24Cty9~G(6cMLBhK$}2E~HiD0?qH%*VmDFiMas*E(c7d7pJ@pGnK;T1x1SETVz1h zS-3Wk{W*A-~>r%00CmTOCMA`=a9?q(O6ww%}}M)H}&=PPvtLX0rUd8xSlYT z11{1h9na_sOTPUDxIB)&m0*ahJxtFZ(M?+&yF4!Lzv&I_6LFi_+uM7a%jGV&zLyS; zJ0Mr{FR#I_^kc6pfE`;v01IHp1RTqYg9Wf-0*>YX0LWeL&9n%)U;qFB07*qoM6N<$ Eg5M4|KmY&$ literal 0 HcmV?d00001 diff --git a/res/drawable/service_notification_icon.png b/res/drawable/service_notification_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..cdc7f77f40a927d5b2b0511d6da6a8be9b48105a GIT binary patch literal 633 zcmV-<0*3vGP)P000>X1^@s6#OZ}&00009a7bBm000XU z000XU0RWnu7ytkQD@jB_R7efoRlSQEQ4pWqRSABlOd+HYEJVdZOKTe~uS>|EAxUXr zX<>Pw*Is9(g{9aC+Sv&wg3T3jSj3NY_hn|1k*o)ak2|lp2XE(n?EHT7oA+fXrQq7d zyfy*h`-G%ctKDq3+fRW&z(I9}jO$v%Fx262u!6zhW24cyM}HS6UFCB5HX4m8(=;h} zFRp|Wi^Wu>Qn@31RD!$GMO|m62ORbMR35F37F64FdmPs<#PE2LB8=s zE|>dB3fk>95-W8)9+l%b%5~j~EB#M?R63mwXCt4_AF%sd2{t@oqg-tzq?`b$h{Y!3 zo)(^G|9nsZ5Rq;nKg+V71?VA>bs#Hk9U}1&E54p^&nM+4U3-pm6_WE*UxnmNl)PUO z&lZ+Lgs1oK19kD{lJg}v;^-j>g+e^;_xojD%hExlf|sMmk4a>qhQlF5A`#f_c3Ov# z{ON+lrvk#^FpNecCJ|gA;Y+94Y(lHm(n=Kp*|rVUY848F0)tPdQ?_*FbA$4i&1SdQ zQ7G3%t5uPN|DVlfE@E*V3r85i^b>3WbzSr zn1hdx=Y3(%|9s-{IMA!;^ZC?zy|xmG#8WDjdN`3_BqWU_zG@NNlag0OKLh&(p|nGY T%<(E>00000NkvXXu0mjf`cEN@ literal 0 HcmV?d00001 diff --git a/src/jackpal/androidterm/TermService.java b/src/jackpal/androidterm/TermService.java index d98aab267..427872c3b 100644 --- a/src/jackpal/androidterm/TermService.java +++ b/src/jackpal/androidterm/TermService.java @@ -69,7 +69,7 @@ public void onCreate() { mTermSessions = new ArrayList(); /* Put the service in the foreground. */ - Notification notification = new Notification(R.drawable.app_terminal, getText(R.string.service_notify_text), System.currentTimeMillis()); + Notification notification = new Notification(R.drawable.service_notification_icon, getText(R.string.service_notify_text), System.currentTimeMillis()); notification.flags |= Notification.FLAG_ONGOING_EVENT; Intent notifyIntent = new Intent(this, Term.class); notifyIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); From f60cb8398812842b4fbbc961630330d702066965 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 22 Oct 2011 05:24:24 -0700 Subject: [PATCH 126/847] A UTF-8 encoded text file From http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-demo.txt This test file shows that we don't currently handle many obscure parts of unicode rendering, including combining diacritics, brail, etc. --- docs/UTF-8-demo.txt | 212 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 212 insertions(+) create mode 100644 docs/UTF-8-demo.txt diff --git a/docs/UTF-8-demo.txt b/docs/UTF-8-demo.txt new file mode 100644 index 000000000..4363f27bd --- /dev/null +++ b/docs/UTF-8-demo.txt @@ -0,0 +1,212 @@ + +UTF-8 encoded sample plain-text file +‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ + +Markus Kuhn [ˈmaʳkʊs kuːn] — 2002-07-25 + + +The ASCII compatible UTF-8 encoding used in this plain-text file +is defined in Unicode, ISO 10646-1, and RFC 2279. + + +Using Unicode/UTF-8, you can write in emails and source code things such as + +Mathematics and sciences: + + ∮ E⋅da = Q, n → ∞, ∑ f(i) = ∏ g(i), ⎧⎡⎛┌─────┐⎞⎤⎫ + ⎪⎢⎜│a²+b³ ⎟⎥⎪ + ∀x∈ℝ: ⌈x⌉ = −⌊−x⌋, α ∧ ¬β = ¬(¬α ∨ β), ⎪⎢⎜│───── ⎟⎥⎪ + ⎪⎢⎜⎷ c₈ ⎟⎥⎪ + ℕ ⊆ ℕ₀ ⊂ ℤ ⊂ ℚ ⊂ ℝ ⊂ ℂ, ⎨⎢⎜ ⎟⎥⎬ + ⎪⎢⎜ ∞ ⎟⎥⎪ + ⊥ < a ≠ b ≡ c ≤ d ≪ ⊤ ⇒ (⟦A⟧ ⇔ ⟪B⟫), ⎪⎢⎜ ⎲ ⎟⎥⎪ + ⎪⎢⎜ ⎳aⁱ-bⁱ⎟⎥⎪ + 2H₂ + O₂ ⇌ 2H₂O, R = 4.7 kΩ, ⌀ 200 mm ⎩⎣⎝i=1 ⎠⎦⎭ + +Linguistics and dictionaries: + + ði ıntəˈnæʃənəl fəˈnɛtık əsoʊsiˈeıʃn + Y [ˈʏpsilɔn], Yen [jɛn], Yoga [ˈjoːgɑ] + +APL: + + ((V⍳V)=⍳⍴V)/V←,V ⌷←⍳→⍴∆∇⊃‾⍎⍕⌈ + +Nicer typography in plain text files: + + ╔══════════════════════════════════════════╗ + ║ ║ + ║ • ‘single’ and “double” quotes ║ + ║ ║ + ║ • Curly apostrophes: “We’ve been here” ║ + ║ ║ + ║ • Latin-1 apostrophe and accents: '´` ║ + ║ ║ + ║ • ‚deutsche‘ „Anführungszeichen“ ║ + ║ ║ + ║ • †, ‡, ‰, •, 3–4, —, −5/+5, ™, … ║ + ║ ║ + ║ • ASCII safety test: 1lI|, 0OD, 8B ║ + ║ ╭─────────╮ ║ + ║ • the euro symbol: │ 14.95 € │ ║ + ║ ╰─────────╯ ║ + ╚══════════════════════════════════════════╝ + +Combining characters: + + STARGΛ̊TE SG-1, a = v̇ = r̈, a⃑ ⊥ b⃑ + +Greek (in Polytonic): + + The Greek anthem: + + Σὲ γνωρίζω ἀπὸ τὴν κόψη + τοῦ σπαθιοῦ τὴν τρομερή, + σὲ γνωρίζω ἀπὸ τὴν ὄψη + ποὺ μὲ βία μετράει τὴ γῆ. + + ᾿Απ᾿ τὰ κόκκαλα βγαλμένη + τῶν ῾Ελλήνων τὰ ἱερά + καὶ σὰν πρῶτα ἀνδρειωμένη + χαῖρε, ὦ χαῖρε, ᾿Ελευθεριά! + + From a speech of Demosthenes in the 4th century BC: + + Οὐχὶ ταὐτὰ παρίσταταί μοι γιγνώσκειν, ὦ ἄνδρες ᾿Αθηναῖοι, + ὅταν τ᾿ εἰς τὰ πράγματα ἀποβλέψω καὶ ὅταν πρὸς τοὺς + λόγους οὓς ἀκούω· τοὺς μὲν γὰρ λόγους περὶ τοῦ + τιμωρήσασθαι Φίλιππον ὁρῶ γιγνομένους, τὰ δὲ πράγματ᾿ + εἰς τοῦτο προήκοντα, ὥσθ᾿ ὅπως μὴ πεισόμεθ᾿ αὐτοὶ + πρότερον κακῶς σκέψασθαι δέον. οὐδέν οὖν ἄλλο μοι δοκοῦσιν + οἱ τὰ τοιαῦτα λέγοντες ἢ τὴν ὑπόθεσιν, περὶ ἧς βουλεύεσθαι, + οὐχὶ τὴν οὖσαν παριστάντες ὑμῖν ἁμαρτάνειν. ἐγὼ δέ, ὅτι μέν + ποτ᾿ ἐξῆν τῇ πόλει καὶ τὰ αὑτῆς ἔχειν ἀσφαλῶς καὶ Φίλιππον + τιμωρήσασθαι, καὶ μάλ᾿ ἀκριβῶς οἶδα· ἐπ᾿ ἐμοῦ γάρ, οὐ πάλαι + γέγονεν ταῦτ᾿ ἀμφότερα· νῦν μέντοι πέπεισμαι τοῦθ᾿ ἱκανὸν + προλαβεῖν ἡμῖν εἶναι τὴν πρώτην, ὅπως τοὺς συμμάχους + σώσομεν. ἐὰν γὰρ τοῦτο βεβαίως ὑπάρξῃ, τότε καὶ περὶ τοῦ + τίνα τιμωρήσεταί τις καὶ ὃν τρόπον ἐξέσται σκοπεῖν· πρὶν δὲ + τὴν ἀρχὴν ὀρθῶς ὑποθέσθαι, μάταιον ἡγοῦμαι περὶ τῆς + τελευτῆς ὁντινοῦν ποιεῖσθαι λόγον. + + Δημοσθένους, Γ´ ᾿Ολυνθιακὸς + +Georgian: + + From a Unicode conference invitation: + + გთხოვთ ახლავე გაიაროთ რეგისტრაცია Unicode-ის მეათე საერთაშორისო + კონფერენციაზე დასასწრებად, რომელიც გაიმართება 10-12 მარტს, + ქ. მაინცში, გერმანიაში. კონფერენცია შეჰკრებს ერთად მსოფლიოს + ექსპერტებს ისეთ დარგებში როგორიცაა ინტერნეტი და Unicode-ი, + ინტერნაციონალიზაცია და ლოკალიზაცია, Unicode-ის გამოყენება + ოპერაციულ სისტემებსა, და გამოყენებით პროგრამებში, შრიფტებში, + ტექსტების დამუშავებასა და მრავალენოვან კომპიუტერულ სისტემებში. + +Russian: + + From a Unicode conference invitation: + + Зарегистрируйтесь сейчас на Десятую Международную Конференцию по + Unicode, которая состоится 10-12 марта 1997 года в Майнце в Германии. + Конференция соберет широкий круг экспертов по вопросам глобального + Интернета и Unicode, локализации и интернационализации, воплощению и + применению Unicode в различных операционных системах и программных + приложениях, шрифтах, верстке и многоязычных компьютерных системах. + +Thai (UCS Level 2): + + Excerpt from a poetry on The Romance of The Three Kingdoms (a Chinese + classic 'San Gua'): + + [----------------------------|------------------------] + ๏ แผ่นดินฮั่นเสื่อมโทรมแสนสังเวช พระปกเกศกองบู๊กู้ขึ้นใหม่ + สิบสองกษัตริย์ก่อนหน้าแลถัดไป สององค์ไซร้โง่เขลาเบาปัญญา + ทรงนับถือขันทีเป็นที่พึ่ง บ้านเมืองจึงวิปริตเป็นนักหนา + โฮจิ๋นเรียกทัพทั่วหัวเมืองมา หมายจะฆ่ามดชั่วตัวสำคัญ + เหมือนขับไสไล่เสือจากเคหา รับหมาป่าเข้ามาเลยอาสัญ + ฝ่ายอ้องอุ้นยุแยกให้แตกกัน ใช้สาวนั้นเป็นชนวนชื่นชวนใจ + พลันลิฉุยกุยกีกลับก่อเหตุ ช่างอาเพศจริงหนาฟ้าร้องไห้ + ต้องรบราฆ่าฟันจนบรรลัย ฤๅหาใครค้ำชูกู้บรรลังก์ ฯ + + (The above is a two-column text. If combining characters are handled + correctly, the lines of the second column should be aligned with the + | character above.) + +Ethiopian: + + Proverbs in the Amharic language: + + ሰማይ አይታረስ ንጉሥ አይከሰስ። + ብላ ካለኝ እንደአባቴ በቆመጠኝ። + ጌጥ ያለቤቱ ቁምጥና ነው። + ደሀ በሕልሙ ቅቤ ባይጠጣ ንጣት በገደለው። + የአፍ ወለምታ በቅቤ አይታሽም። + አይጥ በበላ ዳዋ ተመታ። + ሲተረጉሙ ይደረግሙ። + ቀስ በቀስ፥ ዕንቁላል በእግሩ ይሄዳል። + ድር ቢያብር አንበሳ ያስር። + ሰው እንደቤቱ እንጅ እንደ ጉረቤቱ አይተዳደርም። + እግዜር የከፈተውን ጉሮሮ ሳይዘጋው አይድርም። + የጎረቤት ሌባ፥ ቢያዩት ይስቅ ባያዩት ያጠልቅ። + ሥራ ከመፍታት ልጄን ላፋታት። + ዓባይ ማደሪያ የለው፥ ግንድ ይዞ ይዞራል። + የእስላም አገሩ መካ የአሞራ አገሩ ዋርካ። + ተንጋሎ ቢተፉ ተመልሶ ባፉ። + ወዳጅህ ማር ቢሆን ጨርስህ አትላሰው። + እግርህን በፍራሽህ ልክ ዘርጋ። + +Runes: + + ᚻᛖ ᚳᚹᚫᚦ ᚦᚫᛏ ᚻᛖ ᛒᚢᛞᛖ ᚩᚾ ᚦᚫᛗ ᛚᚪᚾᛞᛖ ᚾᚩᚱᚦᚹᛖᚪᚱᛞᚢᛗ ᚹᛁᚦ ᚦᚪ ᚹᛖᛥᚫ + + (Old English, which transcribed into Latin reads 'He cwaeth that he + bude thaem lande northweardum with tha Westsae.' and means 'He said + that he lived in the northern land near the Western Sea.') + +Braille: + + ⡌⠁⠧⠑ ⠼⠁⠒ ⡍⠜⠇⠑⠹⠰⠎ ⡣⠕⠌ + + ⡍⠜⠇⠑⠹ ⠺⠁⠎ ⠙⠑⠁⠙⠒ ⠞⠕ ⠃⠑⠛⠔ ⠺⠊⠹⠲ ⡹⠻⠑ ⠊⠎ ⠝⠕ ⠙⠳⠃⠞ + ⠱⠁⠞⠑⠧⠻ ⠁⠃⠳⠞ ⠹⠁⠞⠲ ⡹⠑ ⠗⠑⠛⠊⠌⠻ ⠕⠋ ⠙⠊⠎ ⠃⠥⠗⠊⠁⠇ ⠺⠁⠎ + ⠎⠊⠛⠝⠫ ⠃⠹ ⠹⠑ ⠊⠇⠻⠛⠹⠍⠁⠝⠂ ⠹⠑ ⠊⠇⠻⠅⠂ ⠹⠑ ⠥⠝⠙⠻⠞⠁⠅⠻⠂ + ⠁⠝⠙ ⠹⠑ ⠡⠊⠑⠋ ⠍⠳⠗⠝⠻⠲ ⡎⠊⠗⠕⠕⠛⠑ ⠎⠊⠛⠝⠫ ⠊⠞⠲ ⡁⠝⠙ + ⡎⠊⠗⠕⠕⠛⠑⠰⠎ ⠝⠁⠍⠑ ⠺⠁⠎ ⠛⠕⠕⠙ ⠥⠏⠕⠝ ⠰⡡⠁⠝⠛⠑⠂ ⠋⠕⠗ ⠁⠝⠹⠹⠔⠛ ⠙⠑ + ⠡⠕⠎⠑ ⠞⠕ ⠏⠥⠞ ⠙⠊⠎ ⠙⠁⠝⠙ ⠞⠕⠲ + + ⡕⠇⠙ ⡍⠜⠇⠑⠹ ⠺⠁⠎ ⠁⠎ ⠙⠑⠁⠙ ⠁⠎ ⠁ ⠙⠕⠕⠗⠤⠝⠁⠊⠇⠲ + + ⡍⠔⠙⠖ ⡊ ⠙⠕⠝⠰⠞ ⠍⠑⠁⠝ ⠞⠕ ⠎⠁⠹ ⠹⠁⠞ ⡊ ⠅⠝⠪⠂ ⠕⠋ ⠍⠹ + ⠪⠝ ⠅⠝⠪⠇⠫⠛⠑⠂ ⠱⠁⠞ ⠹⠻⠑ ⠊⠎ ⠏⠜⠞⠊⠊⠥⠇⠜⠇⠹ ⠙⠑⠁⠙ ⠁⠃⠳⠞ + ⠁ ⠙⠕⠕⠗⠤⠝⠁⠊⠇⠲ ⡊ ⠍⠊⠣⠞ ⠙⠁⠧⠑ ⠃⠑⠲ ⠔⠊⠇⠔⠫⠂ ⠍⠹⠎⠑⠇⠋⠂ ⠞⠕ + ⠗⠑⠛⠜⠙ ⠁ ⠊⠕⠋⠋⠔⠤⠝⠁⠊⠇ ⠁⠎ ⠹⠑ ⠙⠑⠁⠙⠑⠌ ⠏⠊⠑⠊⠑ ⠕⠋ ⠊⠗⠕⠝⠍⠕⠝⠛⠻⠹ + ⠔ ⠹⠑ ⠞⠗⠁⠙⠑⠲ ⡃⠥⠞ ⠹⠑ ⠺⠊⠎⠙⠕⠍ ⠕⠋ ⠳⠗ ⠁⠝⠊⠑⠌⠕⠗⠎ + ⠊⠎ ⠔ ⠹⠑ ⠎⠊⠍⠊⠇⠑⠆ ⠁⠝⠙ ⠍⠹ ⠥⠝⠙⠁⠇⠇⠪⠫ ⠙⠁⠝⠙⠎ + ⠩⠁⠇⠇ ⠝⠕⠞ ⠙⠊⠌⠥⠗⠃ ⠊⠞⠂ ⠕⠗ ⠹⠑ ⡊⠳⠝⠞⠗⠹⠰⠎ ⠙⠕⠝⠑ ⠋⠕⠗⠲ ⡹⠳ + ⠺⠊⠇⠇ ⠹⠻⠑⠋⠕⠗⠑ ⠏⠻⠍⠊⠞ ⠍⠑ ⠞⠕ ⠗⠑⠏⠑⠁⠞⠂ ⠑⠍⠏⠙⠁⠞⠊⠊⠁⠇⠇⠹⠂ ⠹⠁⠞ + ⡍⠜⠇⠑⠹ ⠺⠁⠎ ⠁⠎ ⠙⠑⠁⠙ ⠁⠎ ⠁ ⠙⠕⠕⠗⠤⠝⠁⠊⠇⠲ + + (The first couple of paragraphs of "A Christmas Carol" by Dickens) + +Compact font selection example text: + + ABCDEFGHIJKLMNOPQRSTUVWXYZ /0123456789 + abcdefghijklmnopqrstuvwxyz £©µÀÆÖÞßéöÿ + –—‘“”„†•…‰™œŠŸž€ ΑΒΓΔΩαβγδω АБВГДабвгд + ∀∂∈ℝ∧∪≡∞ ↑↗↨↻⇣ ┐┼╔╘░►☺♀ fi�⑀₂ἠḂӥẄɐː⍎אԱა + +Greetings in various languages: + + Hello world, Καλημέρα κόσμε, コンニチハ + +Box drawing alignment tests: █ + ▉ + ╔══╦══╗ ┌──┬──┐ ╭──┬──╮ ╭──┬──╮ ┏━━┳━━┓ ┎┒┏┑ ╷ ╻ ┏┯┓ ┌┰┐ ▊ ╱╲╱╲╳╳╳ + ║┌─╨─┐║ │╔═╧═╗│ │╒═╪═╕│ │╓─╁─╖│ ┃┌─╂─┐┃ ┗╃╄┙ ╶┼╴╺╋╸┠┼┨ ┝╋┥ ▋ ╲╱╲╱╳╳╳ + ║│╲ ╱│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╿ │┃ ┍╅╆┓ ╵ ╹ ┗┷┛ └┸┘ ▌ ╱╲╱╲╳╳╳ + ╠╡ ╳ ╞╣ ├╢ ╟┤ ├┼─┼─┼┤ ├╫─╂─╫┤ ┣┿╾┼╼┿┫ ┕┛┖┚ ┌┄┄┐ ╎ ┏┅┅┓ ┋ ▍ ╲╱╲╱╳╳╳ + ║│╱ ╲│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╽ │┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▎ + ║└─╥─┘║ │╚═╤═╝│ │╘═╪═╛│ │╙─╀─╜│ ┃└─╂─┘┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▏ + ╚══╩══╝ └──┴──┘ ╰──┴──╯ ╰──┴──╯ ┗━━┻━━┛ ▗▄▖▛▀▜ └╌╌┘ ╎ ┗╍╍┛ ┋ ▁▂▃▄▅▆▇█ + ▝▀▘▙▄▟ From e12cd186e41d0b32ea4783de3e249a18a57cbca0 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 22 Oct 2011 05:57:23 -0700 Subject: [PATCH 127/847] Add link to Android Market. --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a423eac33..f75a433af 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,8 @@ Emulates a reasonably large subset of Digital Equipment Corporation VT-100 termi that programs like "vi", "Emacs" and "NetHack" will display properly. This code is based on the "Term" application which is included in the Android source code release. -It's provided as a separate project for the convenience of developers who do not want to deal with -installing and building the whole Android source tree. + +[Download the Android Terminal Emulator from Android Market](https://market.android.com/details?id=jackpal.androidterm) Got questions? Please check out the [FAQ](http://github.com/jackpal/Android-Terminal-Emulator/wiki/Frequently-Asked-Questions) @@ -18,4 +18,4 @@ page for recent updates. Not on Market? Don't want to compile your own version? No problem. A fairly recent version of Android Terminal Emulator is here: -[Download Prebuilt version](https://github.com/jackpal/Android-Terminal-Emulator/downloads) +[Download Prebuilt version for side-loading](https://github.com/jackpal/Android-Terminal-Emulator/downloads) From d92a7ed7bb1e6a7d0eab163647aabc989df6d44b Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 22 Oct 2011 18:39:24 -0700 Subject: [PATCH 128/847] Avoid possible overflow if the text ends in a high surrogate. --- src/jackpal/androidterm/EmulatorView.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/jackpal/androidterm/EmulatorView.java b/src/jackpal/androidterm/EmulatorView.java index eff371308..d8d5d9687 100644 --- a/src/jackpal/androidterm/EmulatorView.java +++ b/src/jackpal/androidterm/EmulatorView.java @@ -312,7 +312,14 @@ private void sendText(CharSequence text) { for(int i = 0; i < n; i++) { c = text.charAt(i); if (Character.isHighSurrogate(c)) { - mapAndSend(Character.toCodePoint(c, text.charAt(++i))); + int codePoint; + if (++i < n) { + codePoint = Character.toCodePoint(c, text.charAt(i)); + } else { + // Unicode Replacement Glyph, aka white question mark in black diamond. + codePoint = '\ufffd'; + } + mapAndSend(codePoint); } else { mapAndSend(c); } From b2d747b905222292b97ea9736c70cf83debc6120 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sun, 23 Oct 2011 06:56:22 -0700 Subject: [PATCH 129/847] Make sure we build on API level 10. (Previously our manifest said we used API level 10, but we actually built against API level 11.) --- project.properties | 2 +- src/jackpal/androidterm/EmulatorView.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/project.properties b/project.properties index d79abae19..f049142c1 100644 --- a/project.properties +++ b/project.properties @@ -8,4 +8,4 @@ # project structure. # Project target. -target=android-11 +target=android-10 diff --git a/src/jackpal/androidterm/EmulatorView.java b/src/jackpal/androidterm/EmulatorView.java index d8d5d9687..f88554399 100644 --- a/src/jackpal/androidterm/EmulatorView.java +++ b/src/jackpal/androidterm/EmulatorView.java @@ -41,7 +41,6 @@ import android.view.View; import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.CompletionInfo; -import android.view.inputmethod.CorrectionInfo; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; @@ -442,12 +441,14 @@ public boolean reportFullscreenMode(boolean arg0) { return true; } + /** API Level 11, we are currently at API level 10, so don't use this. public boolean commitCorrection (CorrectionInfo correctionInfo) { if (TermDebug.LOG_IME) { Log.w(TAG, "commitCorrection"); } return true; } + */ public boolean commitText(CharSequence text, int newCursorPosition) { if (TermDebug.LOG_IME) { From 3294c19673f7c023350853c4d93fd6cf15710c2b Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sun, 23 Oct 2011 10:07:28 -0700 Subject: [PATCH 130/847] Revert "Return empty ExtractedText instead of null from getExtractedText()" This reverts commit 2d640ff2381925d9784248712671b7c47f2b7f56. The original commit broke Swype soft keyboard support. --- src/jackpal/androidterm/EmulatorView.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/jackpal/androidterm/EmulatorView.java b/src/jackpal/androidterm/EmulatorView.java index f88554399..abc5c87cf 100644 --- a/src/jackpal/androidterm/EmulatorView.java +++ b/src/jackpal/androidterm/EmulatorView.java @@ -284,7 +284,6 @@ public InputConnection onCreateInputConnection(EditorInfo outAttrs) { outAttrs.inputType = mUseCookedIme ? EditorInfo.TYPE_CLASS_TEXT : EditorInfo.TYPE_NULL; - outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI; return new InputConnection() { private boolean mInBatchEdit; /** @@ -395,7 +394,7 @@ public ExtractedText getExtractedText(ExtractedTextRequest arg0, if (TermDebug.LOG_IME) { Log.w(TAG, "getExtractedText" + arg0 + "," + arg1); } - return new ExtractedText(); + return null; } public CharSequence getTextAfterCursor(int n, int flags) { From 1a64e8696817dcf0e8122fd45db9c1d240e740b0 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sun, 23 Oct 2011 10:13:52 -0700 Subject: [PATCH 131/847] Bump version number. --- AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 17b28447e..24bd66700 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,6 +1,6 @@ From b6db9eff197052ff56b19fecf96fe9feb83ff79a Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sun, 23 Oct 2011 10:29:27 -0700 Subject: [PATCH 132/847] Add a publish checklist. --- docs/releaseChecklist.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 docs/releaseChecklist.md diff --git a/docs/releaseChecklist.md b/docs/releaseChecklist.md new file mode 100644 index 000000000..c3e6c3c7a --- /dev/null +++ b/docs/releaseChecklist.md @@ -0,0 +1,33 @@ +## Android Terminal Emulator Release Checklist + +# Test on 1.5 + +# Test on 1.6 + +# Test on 2.1 + +# Test on 2.2 + +# Test on 2.3 + +# Test on 4.0 + +# Test with Swype + +(Has to be on a real device, Swype beta won't run on an emulator.) + +# Update AndroidManifest.xml version number + +# Tag git branch with version number + +git tag v1.0.xx + +# Push git to repository + +git push +git push --tags + +# Publish to market + +https://market.android.com/publish + From 8c8edf16a1046aef881af4feca5621781814fe23 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sun, 23 Oct 2011 10:49:10 -0700 Subject: [PATCH 133/847] Update checklist. --- docs/releaseChecklist.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/releaseChecklist.md b/docs/releaseChecklist.md index c3e6c3c7a..e381c221d 100644 --- a/docs/releaseChecklist.md +++ b/docs/releaseChecklist.md @@ -1,6 +1,6 @@ ## Android Terminal Emulator Release Checklist -# Test on 1.5 +# Test on 1.5 # Test on 1.6 @@ -31,3 +31,13 @@ git push --tags https://market.android.com/publish +# Update the Android Terminal Emulator Wiki + +https://github.com/jackpal/Android-Terminal-Emulator/wiki/Recent-Updates + +# Upload a new pre-compiled version for people who can't access Market. + +https://github.com/jackpal/Android-Terminal-Emulator/downloads + + + From d7e13467d9ad73a1b964a27bc2b9c173ff0784c0 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Mon, 24 Oct 2011 09:34:18 -0700 Subject: [PATCH 134/847] Update service notification icon. Used "Android Asset Studio" to create the icons from a template. --- docs/notification icon source.png | Bin 1242 -> 3999 bytes .../ic_stat_service_notification_icon.png | Bin 0 -> 765 bytes .../ic_stat_service_notification_icon.png | Bin 0 -> 840 bytes .../ic_stat_service_notification_icon.png | Bin 0 -> 1175 bytes .../ic_stat_service_notification_icon.png | Bin 0 -> 402 bytes .../ic_stat_service_notification_icon.png | Bin 0 -> 429 bytes .../ic_stat_service_notification_icon.png | Bin 0 -> 532 bytes .../ic_stat_service_notification_icon.png | Bin 0 -> 539 bytes .../ic_stat_service_notification_icon.png | Bin 0 -> 561 bytes .../ic_stat_service_notification_icon.png | Bin 0 -> 761 bytes .../ic_stat_service_notification_icon.png | Bin 0 -> 958 bytes .../ic_stat_service_notification_icon.png | Bin 0 -> 1175 bytes .../ic_stat_service_notification_icon.png | Bin 0 -> 1655 bytes res/drawable/service_notification_icon.png | Bin 633 -> 0 bytes src/jackpal/androidterm/TermService.java | 2 +- tools/install_service_icons.sh | 32 ++++++++++++++++++ 16 files changed, 33 insertions(+), 1 deletion(-) create mode 100755 res/drawable-hdpi-v11/ic_stat_service_notification_icon.png create mode 100755 res/drawable-hdpi-v9/ic_stat_service_notification_icon.png create mode 100755 res/drawable-hdpi/ic_stat_service_notification_icon.png create mode 100755 res/drawable-ldpi-v11/ic_stat_service_notification_icon.png create mode 100755 res/drawable-ldpi-v9/ic_stat_service_notification_icon.png create mode 100755 res/drawable-ldpi/ic_stat_service_notification_icon.png create mode 100755 res/drawable-mdpi-v11/ic_stat_service_notification_icon.png create mode 100755 res/drawable-mdpi-v9/ic_stat_service_notification_icon.png create mode 100755 res/drawable-mdpi/ic_stat_service_notification_icon.png create mode 100755 res/drawable-xhdpi-v11/ic_stat_service_notification_icon.png create mode 100755 res/drawable-xhdpi-v9/ic_stat_service_notification_icon.png create mode 100755 res/drawable-xhdpi/ic_stat_service_notification_icon.png delete mode 100644 res/drawable/service_notification_icon.png create mode 100755 tools/install_service_icons.sh diff --git a/docs/notification icon source.png b/docs/notification icon source.png index f120de808e8b072b5a4dba5bdb657fe2e6895d98..03afa71fbf9b01b24e79f50e5c2c2ca0001f31b1 100644 GIT binary patch literal 3999 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 FV1jwWY`Xve delta 1237 zcmV;`1SHY+<{r!&RfeL`X~^X9v6KKspN*5FIx{QO+W zk2?ZMchaKF1O%{S9|&Lp?3jRKe|d4RW5DT3{iUU)hsk8}740s1s4r95FVC+f@S9pc zClZON-rnAss}YIvHf>>H;SPa)T~}8(+||`}HAWtT1PX(Y%;)nxH8nMZf1aJ?&Kdz) zY9A~wFApY(@{8{7Zfn9KbU~oAvy&eT@ir}c(Ioc{2;Apw8nhMTcgX$yeRXL8Es zNT$QlWKc=TOA%A>cS)4-_X#Yve~Sfo10Su0|VN* z`YtpoM{I6xs_E%zRb5@Jf7aI4D!XI(d~9rtcV231Ys-Tj1VMYL%*#TtSXATV<3T#H z>YJFDP&+$2-jb=CxE3&7m8j$6W1UXy`uV-+?CeZCZ!KU}plfWcP}~AgCJ^Ab!LvDE z0c=Rqs)JE6!(2l-3}pg27!?a8DaT0*U_-#D=^(&{IYfGxRlulNf24_4OpZSNsc5GV#XMnF0@3T3p#Yfhy~U4(fW(H$v(9iLlt z*%SvGo(QWBM#T(se+}g@lnLZu^yLsrQie%e1&oRr<{HXjC=j^E#X?ESFlm&DED-|OkbvW1Rvqj( z*HFq^1uSoJeh;8bWDWMSgIcUQ7!`vui&>C3*zkm-rW3#df7p^Rp*%gYjB zczUflIF(9y|7mZSM`VX_-3VkataWf>W20(rZdM~BBaww=GO?DH7OyaeZmhOmM0lv=KwNQixzy*0yQj@j@SV6SzEC zG+ven$JP+Q0@yJD$MWLf{{Xn;_!yCt|M2T0G%t4WE)*H@AavVR#_;6scvHa;NWvrr7Eh^Sadx-@DEL0iQ#jemfpy`W%aWo4zM*w`qh@E=G>A=m_q zhz7w#e4{4U@3-u(bLXDfiO%lj9{AXu*O}j*Gc&u}>2|x>Fxk3c5+f}`Cul<;LLKVY z2ilsfUd5TH%o3!!F*ptq)}}4ud=9=>lW0*pbhy6=uV4_i%p^Bq1acj&7~PM;91PM{ znCv{{TKtq9Aawhx@MYO@5l$_BoezErSE#ern)=n9Cu-kQ_>=F`@EkHr7WLIb`GSh}>hhYqc{mDJ;4Zv{pO9kT7;FlT z3DTfra1SJEKimTCj1&vb8*C_MQ%cUlZkUA|@E%evxW1I@I+Nh1@Dh%|1Z;&=lOQu? zF+pm71GnJ|T!#zLn#EG4r#J~`;0G+h7DzQk$V^*Ikj|OSa00f$LwF1t=N=CfbNf}^ zM6RY7jfn};`S1c}U>v4FX}koxz;&z$O6=eF5``Kw*HBDQ>F+Z)5+hS&G`Y?aSCGyo zw@fi;!G1A8)>UmF zVXfaarJuE2B|?XxaOa(-T)nLI>rA=F^#7$iE3DcLeI2N4!X#=Dw4M}*d>%^>ZTWjf vVQ$-$LjBoP4;>QJwveEIHhv)YQ~_^20!1^5Wv+NsPD9FZBy*c>xQ3 zBa_M8pPZb0?J8)sT34FQ<{bz`SR*D>LKucYI-O2o({ehtlx&gT>9QqO3IEl6KHtK2 z+Yu0OzR_r;0saQiV`Ky808c$i4zUGn#I-L8nA9VZ>vUs?@C(b!%UMUD?a7JpE2Z6K z2WQaFQq{`r?Ch5)akmhecT-j>l}xo-y$IY8n{htxL&MQ77K=lW+#~sBp-?y%fhc(Q zqrP~y@$qrlyhGv$RAy9!5^}UF#qQrgw8{H^7f^!PSzZMY1sv6n1U#WdmwCTof%}lZ z&`FBF1F_QkPA-=#VlP7UiaxH+&(9AG4i3g;mrAAP#Kc6q2Vt13ASO@(4ps00`81o& zR!2rgD(ma(WA%FdD7Fq&1+`jjmC$!R2t1cZAP>N;>FH^KxLvQUt^J^~@ABy@XL*NS z_T*w8t^%YpI|3Ur$;tjXeP(9nMG~Nw&bfC-7aSdV7wQe!SbKr&3}5ZbB?&x090i`yg@uLBmZE>9|sS+&T|i$HDH+D&i-=R=06!1!qL z&g)L;>MgnlZZFt1;8Ti$%$dC*i*RdeYuE;4iQS};S-gnt=~jWmGUuNF%$6j@Rt5Mu zyFN|sHQwXdIt8bjypps7Tc%4Ub@Q79_$?RXGk{<65b7{oi){w_d+U2aFh=q)hoQtx zkP#3>!fZPN7*{YnLXINi-I^|`rJb408KNIfkiK&H>pY)`*+f3jon@^_&&|!12|Auk z{0qHSE|)hTXc&QE#ltZ}pHf3`gRmJGPS1**92SZji6XiZ7b=2lw@R-9u6zSSH{EnUfDa%vxaucx<4$qwh>EBv zf;PkvCmiCCuD_adIaL`tH=T5+7OA>br_TB6uex=s;)%s#l#i((AECgMw_t@*v97Kz zpU30*{Pgq`QuWQUEr;cwB_$=ZYDsKzP~n7NH8nL8KA(@u%gc3pu3roVLzR`4T7YOYI*}BtsHn&fu(!82 z2LC5HVk!~*GMF)ytPmvTh0~N z!a~E8`)8CN930T{@-jU?KRf-U7^pO&p-~#Nw6xIVtkVIfz(F)J7Su@m@WoE8UA?+Wh}T$sTgi8uMvC|FkNoJq`fOJ1efbdDh809ogF$kIibP9LE7EjrOnOF zWa1>OhkvOUo;e1}G8t0|BRMMWgbt{0YHHGs=jZ3?=;(+_OG~9My)R+&OqSq7g5jBC zaL#=VFi=AoPSRL?%V=X`gB~9r>G1GSoAmVbIQHnbzrRnBNQAq`Osa=6 z7X8}VnhuCp9$N;0paPZvpy4A~l3?hQHW-4-HO8_d5ZWZ@-Q69ntgIwsNLX)!!5`0x z^o`EORH9K%r8Gu4l^O|2tV-$6R!p|Vf!Tq1H0GkzBO;1k~^3l;zeL8`WYcQ#jJ&L%fX;k1X3LIlJ~Boqqiz{SPIsM2YDeO>$JXB*5TLDX3*Cf_iOa(sN8hKGms`jWQn z&`plF7(TYQw`pc(hAu8HobCWq7hmT?ZGfqxuK|W$I6>f2n$3i7uDXF6E&--M9WY}m z(TFu6H(EHbofsao@PNySJ(UKSigB*~Ch(u0o@OCj`Yx)gtLa=N*~0(}27}*~(G=DK z>{1>2q?1g`c&Yq;zdo+l)z!^PKL(iEYrgdL_4)ey`#-3cARrBMRaR3NQP;sA>Qwwm p#xNGYZ1~^h-2MUf7o*EtuwPox79z=L&=LRu002ovPDHLkV1lszF$e$v literal 0 HcmV?d00001 diff --git a/res/drawable-ldpi-v11/ic_stat_service_notification_icon.png b/res/drawable-ldpi-v11/ic_stat_service_notification_icon.png new file mode 100755 index 0000000000000000000000000000000000000000..07769a9e1322a4b9d27c913fcae5f8f59e70ac6c GIT binary patch literal 402 zcmV;D0d4+?P)BO)+IF z_`^q6*L(GrdOcH=W!d@S~$`HuIORxcB@Y`VT_#VK1(QD00c3~B6 zz%1@HX$FT2SPm5p@iGgKunr4w1`iMvstlLv**%ztb_f)l4YUX=a0FM7(=dkTp0Ti! zx(s{{W}gGI=fKog8puP_Fb)%ax9lpk*-BfpT<>NRy!16N*)^O(s%#(~-{2BVatyZV z6bC6AC>l8R3HO6e*Y=`++FBbY`KmeRmbM(fwR)f~hDq3h7qF$6k#$|#tk@5-N}2|8 zQrplZ6B$5%hf+<%=U;Wgfeu)e)y47K)*ZY;W#IXfrZ755tGaa^S~Kzv_;fFKH<&7U w$^J_mg4I2RUq9=r5+~*~ngZz`6Dm zzfQtX6vlf?3oTJ=;-U;rCT?y%g7FFP5!iVHSGG>X;KJepZ{Wn_s0+FgYO0Ab44mZl{QJIpzI$(#*4kRf>~+9`-8{jOG5vnuOVc!v_%92B;G@}WMrtq^ zY|Un~8xDV@luGkFcZ-W4wy;Rr;3xQV zNgO~C>z)b6nyuCA`>XPwVyyvtng0wT_bfaUY~pXqe^t(bb`?wPkN%L#|Wf1OU} zm${aVSt^xmkFsDe2<4vPz;PTb77Kc(Uax<$z%X;W-5#&ki*mV)R4S#xT(0{nkicj( zLakQAd_Fgw4cOsu0E=_E9Qyq})W}$^RyZDyP~%)NjYgv=6bkV9d^%5p2F$i??Du

K&2CY^L+wB(dcpS^+Qfnk=%q2^L-DO4~1~?A&YPZ{369V!`BLWH-{pG!3cG*R~ znLL}#gplzBXVZsc3bWJcgmgL$j{3+FiG&V4WX1pwU$j!G=yMNDUYwdqjpUiFl+n0X zOnh{j&8F6qV{#hSi5bn$v6$AY+wEdJ9&2`zWd2R{!`hKZ#Is(n z;rII`e&flAMoqccYQY`#`q%%a+URN~llhY58 zJugH-6oxG#5#JF7StJUfQi(?A4@gM#3O_)lkf?PMoxh+^suZHoP>BYGRY;IX5F!@W z^IkiXF*A4FU2&4f&YU^tyff#{y$VT^H2kMEM~XK_e6_f=W4mitpoxVdbk$PLi!CsJ zffBrF%@r77z5=6K_=DY%y8#D~7y&8I!y;%0H(-vbmYK2QvP>~O&<%<^gt99zfJ1fy zo1k_*z%%3(m?d@&l&h9o0min-1>=2Kh9lU6yf#Rn1U^kd?8azu5@w(u%qbcEYTES= ziwP*`3@%|BIv}r^R@(Ti-RU7cg8DCWH=-gzp)@Mu&l0#NVHb`;@5KbDsDw{JS21RR za7;i&u3;O>FbsMwMu1_a*@tT)VghCtDlRL*RXY0qW7Zv)9R_PL0iD3NAkhz~%!AyR zKnuY^U`9dT9n=dPWDLQAmuXH=O<4U8zGE?gaRS#ofjm=fqSFtI3A7V5<_A`pf3T)Y zpx-_}&V7NZ9pBG$=lc4t!z$cX7HnN}0v%6pZavmY(h|HwZ>4~~!s$EV3hdyVf+CoQ z$}pF;5v3fpO2xMzeUA#xKYd+K-GW*^ZiJ`iw)Yj!IXr2_70_J0v;C8!4pVE=_9)jE dsH0x>lrJlNVu=HCtM~u_002ovPDHLkV1hTA<+lI; literal 0 HcmV?d00001 diff --git a/res/drawable-mdpi-v9/ic_stat_service_notification_icon.png b/res/drawable-mdpi-v9/ic_stat_service_notification_icon.png new file mode 100755 index 0000000000000000000000000000000000000000..0fe44c4d189643997270f94171a65e47e9ae2679 GIT binary patch literal 561 zcmV-10?z%3P)X1^@s6IQ*`u0005_NklphJ+C zjtLuRM6T|-YtM|X_!TX92pX83cW0h?-kEns33#C*l^?+l-sXah%_h8>bnTds&BPTEeVENDM)$4Pm-|s(w{i|B7 zu5~({Kr9w}&tx*zBq0$%*fp*Ix5~+6@}^KIoZ%cXO>ZrlF9y}Ozu_S;BOTpN|(!Jo;@rBW#r+lG@y zMU{*dK0cEj=_g3ufbUX{ZQHz-Qn6S(LH~y!r8Nxz#6@^#vLuMBq^Q;e<}sB@opS=k zKoVq>&rdua?_$9mkHBZzJZ!uN^C5qpzbE(xR#7U7V2VcD00000NkvXXu0mjf%Ut-l literal 0 HcmV?d00001 diff --git a/res/drawable-mdpi/ic_stat_service_notification_icon.png b/res/drawable-mdpi/ic_stat_service_notification_icon.png new file mode 100755 index 0000000000000000000000000000000000000000..635c6fe8356dbc431ff8cb48c55be5806e9a13cc GIT binary patch literal 761 zcmVP000>X1^@s6#OZ}&0008ONklLS6uoLRq9SSp-K>a}m4zUr5CqHp6DcK~e_`3bV7-t!rQH^GniMN2LQ;i*jj)iY zC`gQ-`_6;&m>DO51bi@F=gmFmymRk+GoJl^FUKCCReo8o*D{?>FI2o%B;RZz(P%Us3DrEyF)Xppf)s%fw*jYdOqxtu&b zJ<03qD=|WMTrF_*dRUO&_nM{Z|h`?b{7$8SQtRm)| z-~lfP0r>d%s72w7&)$o4MG@q27$>MB2Av?Do}TLFqbGb$pfzGa*f78#j~8={2v%UN zHGmHf4>B5!u(9n&CdUtQxiDg^d^RUcgb4aTXAaJTTj03;Ap$q9BA5gz2CPRS z5xKm)l()Ax-77wGDxi=sd05Mt%P;Z1>iaBhFYc5{tz~O6dD=IMgnzZ(S$ZNB3}*$z&ifVxAExl}cBILgBYcGG8<%eKx9| r{JX!uzaq|*ixbC)W(yyKoSgpwAEqfhw~#u000000NkvXXu0mjfD$8E7 literal 0 HcmV?d00001 diff --git a/res/drawable-xhdpi-v11/ic_stat_service_notification_icon.png b/res/drawable-xhdpi-v11/ic_stat_service_notification_icon.png new file mode 100755 index 0000000000000000000000000000000000000000..a0e4f60c711455c0497959e7cfc58d3fe4bbdb5b GIT binary patch literal 958 zcmV;v13~A1+6&`QHJiYpA?SsM&tVpti?pqvk*}J`*DcWRI~J}1O_#-f z11tBt9G%F12N9TMKswto82HdQ@U*SSUaRp=0m3b{d|lux@g8 z!u{}$5*Oa@6}NRN-!6hLps9f@0bwOM^a;Mz3l4DH1Sjgh+c`LKE{dspf2S%~1*pS0 za2Srka##bSK%(_B@jN@Ke~Q|T)_2c@$wP^2XVTO!~bT)~O5l$_TZxB!2k zNJ|Q^W(zrd7aqYLSOc3OIY+6}iRB_djYz;KXm`E=18@zrkgbD2)%?xUxdb>~ajxf* z2XGq>!BQy7k`b`10X3qWe-JjpN>Bt=!3b!hE6S1rED?}%YUm}%@olgKhTsUe{ejcm zEtw>&5s=V(b@W6i5vQPNOGdydz<2ZtKW=&#)^UCkw9$no;SWJ6cQTfXfUsjdRtRTW z1H+)f2s>2v8iC1LE>lRIhmQFUDKqJzt1G4ur!3}t>fTPvlYLtSBpd3~(-!w9`?UyY z(RL}9a}l70lw3>&$w~HWiGW_=<`v{%s#$#@iJC=#-b3d{TPdzEd_QmM$+D5@Wk&g2 zxL3lahUypGoK4khb-#aIZ6zO5ECLiIy^XDgSi~goJ7n$1TyD!W0NNn+K_O}u0eTZV z18OujvMSRHjzq@lQEW0j0ViLWECSS<^+Q+bi#;|aOvBXeO4bOJ<)=#m>d!O{t0dqf zP77ztILT!na19oDRH{A;J3TIOp0uh&K#vTs!H>Y(4DbCZ_OCb;pb8p|vQ0bvc{mPA zEb?OjLTgH`p#W8Q20ChED%146Rm5zR!Vi9hFpX;C(0>7s;Rk3Ws(}0&gRAf!8kY4R znF_aq!npy;js#scnVi;_@IlbyO2b+SP=!9|hcHSiE8FxD;Z0VjF#%ciD|f95qubG- gObh5}_;R@UFVJ^(50T89aR2}S07*qoM6N<$g6adcKmY&$ literal 0 HcmV?d00001 diff --git a/res/drawable-xhdpi-v9/ic_stat_service_notification_icon.png b/res/drawable-xhdpi-v9/ic_stat_service_notification_icon.png new file mode 100755 index 0000000000000000000000000000000000000000..a8ace0982ca61bc5ecd36c468858b6036aeed49b GIT binary patch literal 1175 zcmV;I1Zew-P)@9s(Fhn2Ch5<`P5Aq?CtGcDyT}O63))fb}X)MZ^7o~X2JLUr|6HF@G@wt z)vA$c#8;gsJRab)b_4OHoztw0o zEVz3jQS_7{QESXY=KnHGFaVV)YNdQW|7x*V{G9Pe4giyxTCH|X!PM~o1LHq6)V$m6 zItpq{RX@!G_?~#8YS>}sg1C9=N5)UmM=67s%jI0D9RQ31y3@x=gZ}o&$jBEk#&KS5 zHk$_$MghA3_UrY!fpd1{ayh_ph;HTwMxju+5BX;(KjI9c9|xe9!8jmzWps2DU(K5! zeFozXmzI{kLzd}0WuH0S-rjChtJRJcp~YI(5=Tw~dPt2rpj~LU+i$Wjo`&@@4#qP* z56Glcxu}G0PubXIKs8ZtHKVNl0r+=eeFfV078Vw2PT?Fs_`&^2oKqPvfhZk-!8Y_q zO7JZGwVj=vYv+jVMSJ5^3F8v5@!aYO9C8=rsC%9MDM)J=zrrbb_&fm2))s2>7=Q!@ z0_p?eIl$aNQM>UQfc$PoXQ7-DLWEt5VLlS4AVR17x7mDO>Q;nTfGF%?u>z26eHG+GZ1s4!OF_Y_n>~C`oF{Ym&3!uNrFF*gBN=GOGigX zkEd<#HOJ2F0TJ7TaR$VU^)VB5k0V*5-ythLd&B8yjLolZ(&vXeK-VSe$$c0D zu-b@%E~$m(^78T#Wh{IL=R;fgZ0#6iPkw3mNjh6mg`pnDCd#KqTq1xyE*PcXJ%QQ<=~wmGrV? z3D2ns7n$zidW!930npBV{ovr>#{T|(&NgRcC_o)b~fW=|Mlx=*GR0|vkfCbGmFqx=16TL@k!v8ChY4zHO z&MxAe{DXoO+YyYZDf}0;dWvRsY(L=qUsq?m)vIHE6D8<%XbQ%&`TwCjVh<7g30AJV zWrhbXHpj0^n*$Ji`Lq>G>9n9W7R_q?^f}Hi68NU{$g)en+2Hdx`E(c2gn>I39agvsklni);^ zfzGq2nvF%X8Xy(#F0bjwtE;O&p%cacWT3wfsZ-YVoaOIKyy@v_{c+mD*ex;v;7lIR pX>_Tp9R%ADz%T>D3|x5z{sAo0vd}}P=WPH0002ovPDHLkV1h?FOX~mt literal 0 HcmV?d00001 diff --git a/res/drawable-xhdpi/ic_stat_service_notification_icon.png b/res/drawable-xhdpi/ic_stat_service_notification_icon.png new file mode 100755 index 0000000000000000000000000000000000000000..8fb6a951bcc320164b2fb246c62757a5eec168a6 GIT binary patch literal 1655 zcmV--28j8IP)#t{TD0sN*C)6)&v=@rljBEvBBV@R0WQ z_K1&v7jqaI`smFC1R!icii?Y>yStkT3kyRAWOQ_t#>U26V4xr86&;5=vnwEa9}^Qp zot>RjR8$la5YR9^J)OF`x+pI%&oQj753Xf1JOX#(RFG~Z)Sx^awZ3^2VG21a}m2oeIEo#b#z^XmWDWHzySbP`F+i zMCWO{1|KL6Pw=VakU%cx^(M!-X9XfR7{+>D+1lEoxw$#Vd$qN-w6L&1M@L6a_SFRj za(#Y&?f_A1p|sS;FCb7@66zq70Jy!Qql228o2jz0lE%l!X?Av2-43|dJr5@S>f`c= zL=R>X7(xdjP+wo~)IZbH)3md*VnJ=&CmZ2yS4Z%_F*{aI!2fFVp4ae~$wrBO}fjiIXd6C_IAGP0=gb=Xyy8qsTcq1}_&7-I_`m7V64jY-5Iv zk6$ni@^x7@AefWk?*bx~hEfuojd?K2>uZs0^=6ZWIaP}(S_25Ii4Ch;1ccK1Hs*?9 zusvky)`l)N`6nkQhA0CF?m$V|BrufvHafX3kTFVl{h$-P zwnjeZKxyd_sh<@H7TGFLT__61hK2?s1k`=3g)+KT)jfZDR17yaH_jsh=mYkMv_6LRJMj#UUntshK=cCvdRX^}RM2JtN%L0Y=R!+{ z?z;m)@6>I0>a3`!Ab#WSvr9lgP*H-39!B8!mL6GOU#Fg)9zr5SxP1EbiE3(U zd_ZiW1^oi5er@~4Cn?B_{QP|C@9%f2V{G!$(o%yXfJj41Lt}v}w4k)yNcb+nAAcx2 zJKGTQ8UvL5ZEbBnR#8z=zH3};Y;3qrO&bu`qpq@m-lKGZ%Td|BMT7F{r}G~g8X~+y z({)kTIvuW!4?X;qDwz}7iS!5vaoko_RZ)3)xsxz$Rsr2>G$hsS?QQ2JpskvQYzCCV z_fC~O7Z3(vR%41X%z6f60CDXRxDFOUAC67wCZomx!bDGe3_kMd;l<{b#P1CZA%*n7 zd-ahMqVX>q1`tk25B$RIGoK{2sQ0ljT);pVG<+3^)iRHb5&(-F~Iz<%Xoq B7N`IK literal 0 HcmV?d00001 diff --git a/res/drawable/service_notification_icon.png b/res/drawable/service_notification_icon.png deleted file mode 100644 index cdc7f77f40a927d5b2b0511d6da6a8be9b48105a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 633 zcmV-<0*3vGP)P000>X1^@s6#OZ}&00009a7bBm000XU z000XU0RWnu7ytkQD@jB_R7efoRlSQEQ4pWqRSABlOd+HYEJVdZOKTe~uS>|EAxUXr zX<>Pw*Is9(g{9aC+Sv&wg3T3jSj3NY_hn|1k*o)ak2|lp2XE(n?EHT7oA+fXrQq7d zyfy*h`-G%ctKDq3+fRW&z(I9}jO$v%Fx262u!6zhW24cyM}HS6UFCB5HX4m8(=;h} zFRp|Wi^Wu>Qn@31RD!$GMO|m62ORbMR35F37F64FdmPs<#PE2LB8=s zE|>dB3fk>95-W8)9+l%b%5~j~EB#M?R63mwXCt4_AF%sd2{t@oqg-tzq?`b$h{Y!3 zo)(^G|9nsZ5Rq;nKg+V71?VA>bs#Hk9U}1&E54p^&nM+4U3-pm6_WE*UxnmNl)PUO z&lZ+Lgs1oK19kD{lJg}v;^-j>g+e^;_xojD%hExlf|sMmk4a>qhQlF5A`#f_c3Ov# z{ON+lrvk#^FpNecCJ|gA;Y+94Y(lHm(n=Kp*|rVUY848F0)tPdQ?_*FbA$4i&1SdQ zQ7G3%t5uPN|DVlfE@E*V3r85i^b>3WbzSr zn1hdx=Y3(%|9s-{IMA!;^ZC?zy|xmG#8WDjdN`3_BqWU_zG@NNlag0OKLh&(p|nGY T%<(E>00000NkvXXu0mjf`cEN@ diff --git a/src/jackpal/androidterm/TermService.java b/src/jackpal/androidterm/TermService.java index 427872c3b..d721552ce 100644 --- a/src/jackpal/androidterm/TermService.java +++ b/src/jackpal/androidterm/TermService.java @@ -69,7 +69,7 @@ public void onCreate() { mTermSessions = new ArrayList(); /* Put the service in the foreground. */ - Notification notification = new Notification(R.drawable.service_notification_icon, getText(R.string.service_notify_text), System.currentTimeMillis()); + Notification notification = new Notification(R.drawable.ic_stat_service_notification_icon, getText(R.string.service_notify_text), System.currentTimeMillis()); notification.flags |= Notification.FLAG_ONGOING_EVENT; Intent notifyIntent = new Intent(this, Term.class); notifyIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); diff --git a/tools/install_service_icons.sh b/tools/install_service_icons.sh new file mode 100755 index 000000000..ec5e91678 --- /dev/null +++ b/tools/install_service_icons.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# Copy the service icons from the download location into the resource tree + +if [ $# -ne 2 ] ; then + echo "Expected 2 arguments (source directory, destination directory)" + exit 1 +fi + +SRC=$1 +DST=$2 + +if [ ! -d "$1" ] ; then + echo "Expected first argument to be a source directory. Got '$1'" + exit 2 +fi + +if [ ! -d "$2" ] ; then + echo "Expected second argument to be a destination directory. Got '$2'" + exit 2 +fi + +echo "Copying assets from $1 to $2" + +ASSET="ic_stat_service_notification_icon.png" + +for i in $(ls $SRC); do + DSTDIR="$DST/$i" + if [ ! -d "$DSTDIR" ] ; then + mkdir "$DSTDIR" + fi + cp "$SRC/$i/$ASSET" "$DSTDIR/$ASSET" +done \ No newline at end of file From 9527e594b9a53b197ee2f0de99fd9025961edcf8 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Mon, 24 Oct 2011 09:41:31 -0700 Subject: [PATCH 135/847] Add notification icon for use with Android 1.5 --- .../ic_stat_service_notification_icon.png | Bin 0 -> 1175 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100755 res/drawable/ic_stat_service_notification_icon.png diff --git a/res/drawable/ic_stat_service_notification_icon.png b/res/drawable/ic_stat_service_notification_icon.png new file mode 100755 index 0000000000000000000000000000000000000000..b8f6665cb0a1f70a17b61c2fcc99400acaf85069 GIT binary patch literal 1175 zcmV;I1Zew-P)**92SZji6XiZ7b=2lw@R-9u6zSSH{EnUfDa%vxaucx<4$qwh>EBv zf;PkvCmiCCuD_adIaL`tH=T5+7OA>br_TB6uex=s;)%s#l#i((AECgMw_t@*v97Kz zpU30*{Pgq`QuWQUEr;cwB_$=ZYDsKzP~n7NH8nL8KA(@u%gc3pu3roVLzR`4T7YOYI*}BtsHn&fu(!82 z2LC5HVk!~*GMF)ytPmvTh0~N z!a~E8`)8CN930T{@-jU?KRf-U7^pO&p-~#Nw6xIVtkVIfz(F)J7Su@m@WoE8UA?+Wh}T$sTgi8uMvC|FkNoJq`fOJ1efbdDh809ogF$kIibP9LE7EjrOnOF zWa1>OhkvOUo;e1}G8t0|BRMMWgbt{0YHHGs=jZ3?=;(+_OG~9My)R+&OqSq7g5jBC zaL#=VFi=AoPSRL?%V=X`gB~9r>G1GSoAmVbIQHnbzrRnBNQAq`Osa=6 z7X8}VnhuCp9$N;0paPZvpy4A~l3?hQHW-4-HO8_d5ZWZ@-Q69ntgIwsNLX)!!5`0x z^o`EORH9K%r8Gu4l^O|2tV-$6R!p|Vf!Tq1H0GkzBO;1k~^3l;zeL8`WYcQ#jJ&L%fX;k1X3LIlJ~Boqqiz{SPIsM2YDeO>$JXB*5TLDX3*Cf_iOa(sN8hKGms`jWQn z&`plF7(TYQw`pc(hAu8HobCWq7hmT?ZGfqxuK|W$I6>f2n$3i7uDXF6E&--M9WY}m z(TFu6H(EHbofsao@PNySJ(UKSigB*~Ch(u0o@OCj`Yx)gtLa=N*~0(}27}*~(G=DK z>{1>2q?1g`c&Yq;zdo+l)z!^PKL(iEYrgdL_4)ey`#-3cARrBMRaR3NQP;sA>Qwwm p#xNGYZ1~^h-2MUf7o*EtuwPox79z=L&=LRu002ovPDHLkV1lszF$e$v literal 0 HcmV?d00001 From 7219cbcee17cd8b094457861bbe226416c745de5 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Mon, 24 Oct 2011 09:41:58 -0700 Subject: [PATCH 136/847] Update documentation checklist. --- docs/releaseChecklist.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/docs/releaseChecklist.md b/docs/releaseChecklist.md index e381c221d..11744f8e0 100644 --- a/docs/releaseChecklist.md +++ b/docs/releaseChecklist.md @@ -31,6 +31,30 @@ git push --tags https://market.android.com/publish +The Android Publish UI is error prone: + +1) Visit https://market.android.com/publish/Home#AppEditorPlace:p=jackpal.androidterm + +2) Click on the APK files tab + +3) Upload your new APK. + +4) Activate it by clicking on the Activate link + +5) Click on the "Save" button. + +6) Click on the "Product Details button". + +7) Fill in the "Listing Details" for the new version. + +8) Click on the "Save" button + +9) Visit https://market.android.com/publish/Home and verify that the new version is listed as the current version. + +10) Verify that Market is serving the new version (check the "What's New" portion.) + +https://market.android.com/details?id=jackpal.androidterm + # Update the Android Terminal Emulator Wiki https://github.com/jackpal/Android-Terminal-Emulator/wiki/Recent-Updates From ee721c5a179b425dbd2395d0f501cd63e096f31b Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Mon, 24 Oct 2011 09:50:26 -0700 Subject: [PATCH 137/847] Create Android 1.5 version of notification icon. Use MDPI version, as Android 1.5 devices are defined to be MDPI. --- .../ic_stat_service_notification_icon.png | Bin 1175 -> 761 bytes tools/install_service_icons.sh | 6 +++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/res/drawable/ic_stat_service_notification_icon.png b/res/drawable/ic_stat_service_notification_icon.png index b8f6665cb0a1f70a17b61c2fcc99400acaf85069..635c6fe8356dbc431ff8cb48c55be5806e9a13cc 100755 GIT binary patch literal 761 zcmVP000>X1^@s6#OZ}&0008ONklLS6uoLRq9SSp-K>a}m4zUr5CqHp6DcK~e_`3bV7-t!rQH^GniMN2LQ;i*jj)iY zC`gQ-`_6;&m>DO51bi@F=gmFmymRk+GoJl^FUKCCReo8o*D{?>FI2o%B;RZz(P%Us3DrEyF)Xppf)s%fw*jYdOqxtu&b zJ<03qD=|WMTrF_*dRUO&_nM{Z|h`?b{7$8SQtRm)| z-~lfP0r>d%s72w7&)$o4MG@q27$>MB2Av?Do}TLFqbGb$pfzGa*f78#j~8={2v%UN zHGmHf4>B5!u(9n&CdUtQxiDg^d^RUcgb4aTXAaJTTj03;Ap$q9BA5gz2CPRS z5xKm)l()Ax-77wGDxi=sd05Mt%P;Z1>iaBhFYc5{tz~O6dD=IMgnzZ(S$ZNB3}*$z&ifVxAExl}cBILgBYcGG8<%eKx9| r{JX!uzaq|*ixbC)W(yyKoSgpwAEqfhw~#u000000NkvXXu0mjfD$8E7 literal 1175 zcmV;I1Zew-P)**92SZji6XiZ7b=2lw@R-9u6zSSH{EnUfDa%vxaucx<4$qwh>EBv zf;PkvCmiCCuD_adIaL`tH=T5+7OA>br_TB6uex=s;)%s#l#i((AECgMw_t@*v97Kz zpU30*{Pgq`QuWQUEr;cwB_$=ZYDsKzP~n7NH8nL8KA(@u%gc3pu3roVLzR`4T7YOYI*}BtsHn&fu(!82 z2LC5HVk!~*GMF)ytPmvTh0~N z!a~E8`)8CN930T{@-jU?KRf-U7^pO&p-~#Nw6xIVtkVIfz(F)J7Su@m@WoE8UA?+Wh}T$sTgi8uMvC|FkNoJq`fOJ1efbdDh809ogF$kIibP9LE7EjrOnOF zWa1>OhkvOUo;e1}G8t0|BRMMWgbt{0YHHGs=jZ3?=;(+_OG~9My)R+&OqSq7g5jBC zaL#=VFi=AoPSRL?%V=X`gB~9r>G1GSoAmVbIQHnbzrRnBNQAq`Osa=6 z7X8}VnhuCp9$N;0paPZvpy4A~l3?hQHW-4-HO8_d5ZWZ@-Q69ntgIwsNLX)!!5`0x z^o`EORH9K%r8Gu4l^O|2tV-$6R!p|Vf!Tq1H0GkzBO;1k~^3l;zeL8`WYcQ#jJ&L%fX;k1X3LIlJ~Boqqiz{SPIsM2YDeO>$JXB*5TLDX3*Cf_iOa(sN8hKGms`jWQn z&`plF7(TYQw`pc(hAu8HobCWq7hmT?ZGfqxuK|W$I6>f2n$3i7uDXF6E&--M9WY}m z(TFu6H(EHbofsao@PNySJ(UKSigB*~Ch(u0o@OCj`Yx)gtLa=N*~0(}27}*~(G=DK z>{1>2q?1g`c&Yq;zdo+l)z!^PKL(iEYrgdL_4)ey`#-3cARrBMRaR3NQP;sA>Qwwm p#xNGYZ1~^h-2MUf7o*EtuwPox79z=L&=LRu002ovPDHLkV1lszF$e$v diff --git a/tools/install_service_icons.sh b/tools/install_service_icons.sh index ec5e91678..c92d9ee77 100755 --- a/tools/install_service_icons.sh +++ b/tools/install_service_icons.sh @@ -29,4 +29,8 @@ for i in $(ls $SRC); do mkdir "$DSTDIR" fi cp "$SRC/$i/$ASSET" "$DSTDIR/$ASSET" -done \ No newline at end of file +done + +# Create the Android 1.5 version of the icon + +cp "$SRC/drawable-mdpi/$ASSET" "$DST/drawable/ASSET" From c60fa67b2ea6d5f5c5904ad325442f95de02c17a Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Mon, 24 Oct 2011 10:05:48 -0700 Subject: [PATCH 138/847] Declare version 1.0.35 --- AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 24bd66700..c4b7d2fc7 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,6 +1,6 @@ From 12a655c62d64a434bfc4a126cf8a383858097ef7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20B=C3=B6hm?= Date: Thu, 3 Nov 2011 19:17:21 +0100 Subject: [PATCH 139/847] AndroidTerm: Updated German Translation Change-Id: I29bc56087bbad169f9fcd0b1ff836d9a6e05bd0a --- res/values-de/arrays.xml | 12 +++++++++++- res/values-de/strings.xml | 11 +++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/res/values-de/arrays.xml b/res/values-de/arrays.xml index d0eb2e9c8..d76607a6e 100644 --- a/res/values-de/arrays.xml +++ b/res/values-de/arrays.xml @@ -56,7 +56,17 @@ Trackball - \@ Taste + \@-Taste + Linke Alt-Taste + Rechte Alt-Taste + Lauter + Leiser + Kamera-Taste + + + + Trackball + \@-Taste Linke Alt-Taste Rechte Alt-Taste Lauter diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml index b80467749..e76ded98b 100644 --- a/res/values-de/strings.xml +++ b/res/values-de/strings.xml @@ -22,11 +22,18 @@ Spezialtasten Tastatur an/aus + Aktiviere WakeLock + Deaktiviere WakeLock + Aktiviere WifiLock + Deaktiviere WifiLock + Text bearbeiten Text selektieren Alles kopieren Einfügen + Terminal-Sitzung läuft + Bildschirm @@ -58,6 +65,10 @@ Die Steuerungstaste auswählen. Steuerungstaste + Fn-Taste + Die Fn-Taste auswählen. + Fn-Taste + Eingabemethode Die Eingabemethode für die Soft-Tastatur auswählen. Eingabemethode From f1be3cf02be89e140fbc1e9a4f018dd5e316c632 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Sun, 6 Nov 2011 01:24:31 -0700 Subject: [PATCH 140/847] Fix fast resize when screen shrinks When the line containing the cursor is blank, and the cursor is not at the bottom edge of the screen, fast resize will move the line containing the cursor off the screen. Fix this by ensuring that fast resize never considers lines at or above the cursor for shrinking. This patch prevents a crash which occurs when a full resize operation is conducted immediately after this bug is triggered (the full resize attempts to fetch the character at the cursor, which is off the screen, causing an IllegalArgumentException). Signed-off-by: Jack Palevich --- src/jackpal/androidterm/util/UnicodeTranscript.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jackpal/androidterm/util/UnicodeTranscript.java b/src/jackpal/androidterm/util/UnicodeTranscript.java index 120f83431..169b0c461 100644 --- a/src/jackpal/androidterm/util/UnicodeTranscript.java +++ b/src/jackpal/androidterm/util/UnicodeTranscript.java @@ -173,7 +173,7 @@ public boolean resize(int newColumns, int newRows, int[] cursor) { /* When shrinking the screen, we want to hide blank lines at the bottom in preference to lines at the top of the screen */ Object[] lines = mLines; - for (int i = screenRows - 1; i >= 0; --i) { + for (int i = screenRows - 1; i > cursor[1]; --i) { int index = externalToInternalRow(i); if (lines[index] == null) { // Line is blank From 3ae0bdc7349b0b7ba6ff8fa5fcdf1dc2c138217e Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Sun, 6 Nov 2011 01:24:40 -0700 Subject: [PATCH 141/847] Make changes to UTF-8 preference take effect immediately in running sessions Add a updatePrefs() method in TermSession and TerminalEmulator and call it from Term's updatePrefs(), so that changes to the UTF-8 preference will be picked up immediately by running terminal sessions. Note that a terminal emulator will ignore the UTF-8 preference once any application running in it uses one of the ESC % codes to request a specific character set. Signed-off-by: Jack Palevich --- src/jackpal/androidterm/Term.java | 5 +++++ .../androidterm/session/TermSession.java | 5 +++++ .../androidterm/session/TerminalEmulator.java | 17 +++++++++++++++++ 3 files changed, 27 insertions(+) diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index c1b038d6d..32a642b18 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -218,6 +218,11 @@ private void updatePrefs() { ((EmulatorView) v).setDensity(metrics); ((EmulatorView) v).updatePrefs(mSettings); } + if (mTermSessions != null) { + for (TermSession session : mTermSessions) { + session.updatePrefs(mSettings); + } + } { Window win = getWindow(); WindowManager.LayoutParams params = win.getAttributes(); diff --git a/src/jackpal/androidterm/session/TermSession.java b/src/jackpal/androidterm/session/TermSession.java index a649c148a..ff8ac2eca 100644 --- a/src/jackpal/androidterm/session/TermSession.java +++ b/src/jackpal/androidterm/session/TermSession.java @@ -297,6 +297,11 @@ private void readFromProcess() { } } + public void updatePrefs(TermSettings settings) { + mSettings = settings; + mEmulator.updatePrefs(settings); + } + public void finish() { Exec.hangupProcessGroup(mProcId); Exec.close(mTermFd); diff --git a/src/jackpal/androidterm/session/TerminalEmulator.java b/src/jackpal/androidterm/session/TerminalEmulator.java index 2167a2aa4..f1812191f 100644 --- a/src/jackpal/androidterm/session/TerminalEmulator.java +++ b/src/jackpal/androidterm/session/TerminalEmulator.java @@ -271,6 +271,7 @@ public class TerminalEmulator { */ private static final int UNICODE_REPLACEMENT_CHAR = 0xfffd; private boolean mUTF8Mode = false; + private boolean mUTF8EscapeUsed = false; private int mUTF8ToFollow = 0; private ByteBuffer mUTF8ByteBuffer; private CharBuffer mInputCharBuffer; @@ -639,9 +640,11 @@ private void doEscPercent(byte b) { switch (b) { case '@': // Esc % @ -- return to ISO 2022 mode mUTF8Mode = false; + mUTF8EscapeUsed = true; break; case 'G': // Esc % G -- UTF-8 mode mUTF8Mode = true; + mUTF8EscapeUsed = true; break; default: // unimplemented character set break; @@ -1447,11 +1450,25 @@ public void reset() { blockClear(0, 0, mColumns, mRows); mUTF8Mode = mTermSettings.defaultToUTF8Mode(); + mUTF8EscapeUsed = false; mUTF8ToFollow = 0; mUTF8ByteBuffer.clear(); mInputCharBuffer.clear(); } + public void updatePrefs(TermSettings settings) { + mTermSettings = settings; + if (!mUTF8EscapeUsed) { + boolean newUTF8Mode = settings.defaultToUTF8Mode(); + if (mUTF8Mode && !newUTF8Mode) { + mUTF8ToFollow = 0; + mUTF8ByteBuffer.clear(); + mInputCharBuffer.clear(); + } + mUTF8Mode = newUTF8Mode; + } + } + public String getSelectedText(int x1, int y1, int x2, int y2) { return mScreen.getSelectedText(x1, y1, x2, y2); } From 6ea8f282867f3336ec1c9ee0cc9ec7a2a32a78ca Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Sun, 6 Nov 2011 01:25:10 -0700 Subject: [PATCH 142/847] Color tweaks Other terminals which offer color support generally have two sets of colors, a "dim" set for regular text and backgrounds and a "bright" set for use with bold text. At the moment, Android Terminal Emulator uses the same set of eight colors for backgrounds, normal text, and bold text, which causes one notable problem: applications generally expect bold text colored black to be displayed as "bright black" (a dark gray), but this text is invisible in Android Terminal Emulator in the white-on-black configuration. Fix this by extending the array of foreground colors to include "bright" colors (actually, the same colors we were using before, except for the addition of a "bright black"), taking advantage of the fact that we're using the high bit of the foreground color to store bold information. We dim the regular set of colors slightly, to distinguish them from the bright colors. Signed-off-by: Jack Palevich --- src/jackpal/androidterm/EmulatorView.java | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/jackpal/androidterm/EmulatorView.java b/src/jackpal/androidterm/EmulatorView.java index abc5c87cf..0788ea47f 100644 --- a/src/jackpal/androidterm/EmulatorView.java +++ b/src/jackpal/androidterm/EmulatorView.java @@ -992,13 +992,21 @@ public String getSelectedText() { abstract class BaseTextRenderer implements TextRenderer { protected int[] mForePaint = { 0xff000000, // Black - 0xffff0000, // Red + 0xffcc0000, // Red + 0xff00cc00, // green + 0xffcccc00, // yellow + 0xff0000cc, // blue + 0xffcc00cc, // magenta + 0xff00cccc, // cyan + 0xffcccccc, // "white"/light gray -- is overridden by constructor + 0xff666666, // bright black/dark gray + 0xffff0000, // red 0xff00ff00, // green 0xffffff00, // yellow 0xff0000ff, // blue 0xffff00ff, // magenta 0xff00ffff, // cyan - 0xffffffff // white -- is overridden by constructor + 0xffffffff // white }; protected int[] mBackPaint = { 0xff000000, // Black -- is overridden by constructor @@ -1049,7 +1057,7 @@ public int getCharacterHeight() { public void drawTextRun(Canvas canvas, float x, float y, int lineOffset, int runWidth, char[] text, int index, int count, boolean cursor, int foreColor, int backColor) { - setColorMatrix(mForePaint[foreColor & 7], + setColorMatrix(mForePaint[foreColor], cursor ? mCursorPaint : mBackPaint[backColor & 7]); int destX = (int) x + kCharacterWidth * lineOffset; int destY = (int) y; @@ -1132,7 +1140,7 @@ public void drawTextRun(Canvas canvas, float x, float y, int lineOffset, if (underline) { mTextPaint.setUnderlineText(true); } - mTextPaint.setColor(mForePaint[foreColor & 0x7]); + mTextPaint.setColor(mForePaint[foreColor]); canvas.drawText(text, index, count, left, y, mTextPaint); if (bold) { mTextPaint.setFakeBoldText(false); From 17bd2c33a418eeff8f72d4059875f25ab340d83d Mon Sep 17 00:00:00 2001 From: Eug89 Date: Mon, 14 Nov 2011 19:20:22 +0100 Subject: [PATCH 143/847] androidterm: Updated Italian translations Change-Id: I1f9ab706fce9b79b792406b298b811cecd5b626b --- res/values-it/strings.xml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml index 20fbccc4b..91f91a55e 100644 --- a/res/values-it/strings.xml +++ b/res/values-it/strings.xml @@ -22,6 +22,11 @@ Combinazione tasti Attiva/disattiva keyboard + Attiva WakeLock + Disatt. WakeLock + Attiva WifiLock + Disatt. WifiLock + Modifica testo Copia tutto Incolla @@ -57,6 +62,10 @@ Scegli tasto control. Tasto control + Tasto Fn + Scegli tasto Fn. + Tasto Fn + Metodo inserimento Scegli il metodo di inserimento per la tastiera virtuale. Metodo inserimento From 91bdf635a47f78209bbf3fbd6ef7f6b9f9031df6 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Fri, 18 Nov 2011 02:24:11 -0800 Subject: [PATCH 144/847] Simplify Android compatibility class * Make names less cumbersome * Use lazy loading instead of reflection to simplify code Signed-off-by: Jack Palevich --- .../androidterm/util/AndroidCompat.java | 55 ++++++++++++++++ .../androidterm/util/PostAndroid3Utils.java | 65 ------------------- .../androidterm/util/UnicodeTranscript.java | 12 +--- 3 files changed, 58 insertions(+), 74 deletions(-) create mode 100644 src/jackpal/androidterm/util/AndroidCompat.java delete mode 100644 src/jackpal/androidterm/util/PostAndroid3Utils.java diff --git a/src/jackpal/androidterm/util/AndroidCompat.java b/src/jackpal/androidterm/util/AndroidCompat.java new file mode 100644 index 000000000..85622d35f --- /dev/null +++ b/src/jackpal/androidterm/util/AndroidCompat.java @@ -0,0 +1,55 @@ +package jackpal.androidterm.util; + +import android.text.AndroidCharacter; + +/** + * Provides APIs post Android version 3. + * + */ +public class AndroidCompat { + public final static int SDK = + Integer.valueOf(android.os.Build.VERSION.SDK); + + /** + * The classes here each take advantage of the fact that the VM does + * not attempt to load a class until it's accessed, and the verifier + * does not run until a class is loaded. By keeping the methods which + * are unavailable on older platforms in subclasses which are only ever + * accessed on platforms where they are available, we can preserve + * compatibility with older platforms without resorting to reflection. + * + * See http://developer.android.com/resources/articles/backward-compatibility.html + * and http://android-developers.blogspot.com/2010/07/how-to-have-your-cupcake-and-eat-it-too.html + * for further discussion of this technique. + */ + + /** + * Definitions related to android.text.AndroidCharacter + */ + public static class AndroidCharacterComp { + public static final int EAST_ASIAN_WIDTH_NEUTRAL = 0; + public static final int EAST_ASIAN_WIDTH_AMBIGUOUS = 1; + public static final int EAST_ASIAN_WIDTH_HALF_WIDTH = 2; + public static final int EAST_ASIAN_WIDTH_FULL_WIDTH = 3; + public static final int EAST_ASIAN_WIDTH_NARROW = 4; + public static final int EAST_ASIAN_WIDTH_WIDE = 5; + + private static class Api8OrLater { + public static void initialize() { + // Does nothing -- call this to force the class to try to load + } + + public static int getEastAsianWidth(char c) { + return AndroidCharacter.getEastAsianWidth(c); + } + } + + public static int getEastAsianWidth(char c) { + if (SDK >= 8) { + return Api8OrLater.getEastAsianWidth(c); + } else { + return EAST_ASIAN_WIDTH_NARROW; + } + } + } +} diff --git a/src/jackpal/androidterm/util/PostAndroid3Utils.java b/src/jackpal/androidterm/util/PostAndroid3Utils.java deleted file mode 100644 index 0f28de23b..000000000 --- a/src/jackpal/androidterm/util/PostAndroid3Utils.java +++ /dev/null @@ -1,65 +0,0 @@ -package jackpal.androidterm.util; - -import java.lang.reflect.Method; - -import android.text.AndroidCharacter; -import android.util.Log; - -/** - * Provides APIs post Android version 3. - * - */ -public class PostAndroid3Utils { - private static String TAG = "PostAndroid3Utils"; - - public final static int SDK = - Integer.valueOf(android.os.Build.VERSION.SDK); - /** - * Definitions related to android.text.AndroidCharacter - */ - public static class AndroidCharacterComp { - public static final int EAST_ASIAN_WIDTH_NEUTRAL = 0; - public static final int EAST_ASIAN_WIDTH_AMBIGUOUS = 1; - public static final int EAST_ASIAN_WIDTH_HALF_WIDTH = 2; - public static final int EAST_ASIAN_WIDTH_FULL_WIDTH = 3; - public static final int EAST_ASIAN_WIDTH_NARROW = 4; - public static final int EAST_ASIAN_WIDTH_WIDE = 5; - - private static boolean mGetEastAsianWidthInitialized; - private static Method mGetEastAsianWidthMethod; - /** - * Calls AndroidCharacter.getEastAsianWidth if it exists, - * otherwise returns EAST_ASIAN_WIDTH_NARROW. - */ - public static int getEastAsianWidth(char c) { - int result = EAST_ASIAN_WIDTH_NARROW; - if (!mGetEastAsianWidthInitialized) { - mGetEastAsianWidthInitialized = true; - try { - Class[] parameterTypes = new Class[]{ - char.class}; - Method method = AndroidCharacter.class.getMethod( - "getEastAsianWidth", parameterTypes); - if (method.getGenericReturnType() != int.class) { - Log.e(TAG, "Unexpected return type for getEastAsianWidth"); - } else { - mGetEastAsianWidthMethod = method; - } - } catch (NoSuchMethodException e) { - // Pre Android API level 8 - } - } - - if (mGetEastAsianWidthMethod != null) { - try { - Integer objectResult = (Integer) mGetEastAsianWidthMethod.invoke( - null, new Object[]{new Character(c)}); - result = objectResult.intValue(); - } catch(Exception e) { - Log.e(TAG, "Unexpected exception when calling getEastAsianWidth", e); - } - } - return result; - } - } -} diff --git a/src/jackpal/androidterm/util/UnicodeTranscript.java b/src/jackpal/androidterm/util/UnicodeTranscript.java index 169b0c461..d77a23dce 100644 --- a/src/jackpal/androidterm/util/UnicodeTranscript.java +++ b/src/jackpal/androidterm/util/UnicodeTranscript.java @@ -545,17 +545,11 @@ public static int charWidth(int codePoint) { return 0; } - if (PostAndroid3Utils.SDK < 8) { - /* No East Asian wide char support when running on Android < 2.2, - because getEastAsianWidth() was introduced in API 8 */ - return 1; - } - if (Character.charCount(codePoint) == 1) { // Android's getEastAsianWidth() only works for BMP characters - switch (PostAndroid3Utils.AndroidCharacterComp.getEastAsianWidth((char) codePoint)) { - case PostAndroid3Utils.AndroidCharacterComp.EAST_ASIAN_WIDTH_FULL_WIDTH: - case PostAndroid3Utils.AndroidCharacterComp.EAST_ASIAN_WIDTH_WIDE: + switch (AndroidCompat.AndroidCharacterComp.getEastAsianWidth((char) codePoint)) { + case AndroidCompat.AndroidCharacterComp.EAST_ASIAN_WIDTH_FULL_WIDTH: + case AndroidCompat.AndroidCharacterComp.EAST_ASIAN_WIDTH_WIDE: return 2; } } else { From 47e5ef95b24e1eb88fbed8d3987367178fb5be2a Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Fri, 18 Nov 2011 02:24:19 -0800 Subject: [PATCH 145/847] Make window title string localizable Signed-off-by: Jack Palevich --- res/values/strings.xml | 2 ++ src/jackpal/androidterm/TermViewFlipper.java | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 0e1555aae..4bf613d33 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -37,6 +37,8 @@ Copy all Paste + Window %1$d + Terminal session is running diff --git a/src/jackpal/androidterm/TermViewFlipper.java b/src/jackpal/androidterm/TermViewFlipper.java index 1a1925740..c21f31e7d 100644 --- a/src/jackpal/androidterm/TermViewFlipper.java +++ b/src/jackpal/androidterm/TermViewFlipper.java @@ -78,7 +78,7 @@ private void showTitle() { if (getChildCount() == 0) { return; } - String title = "Window " + (getDisplayedChild()+1); + String title = context.getString(R.string.window_title, getDisplayedChild()+1); if (mToast == null) { mToast = Toast.makeText(context, title, Toast.LENGTH_SHORT); } else { From b9a4e3d332a99c0ade8d122873e9fad15cd9a9b5 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Fri, 18 Nov 2011 02:24:29 -0800 Subject: [PATCH 146/847] Slow down scrolling by vertical fling As of now, a vertical fling scrolls the screen far too quickly to be useful -- even a small flick of the finger scrolls the screen by hundreds of lines. Reduce the coefficient converting fling velocity to scroll distance from 2 to 0.1, which results in much more reasonable scroll distances. Signed-off-by: Jack Palevich --- src/jackpal/androidterm/EmulatorView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jackpal/androidterm/EmulatorView.java b/src/jackpal/androidterm/EmulatorView.java index 0788ea47f..9dad89c33 100644 --- a/src/jackpal/androidterm/EmulatorView.java +++ b/src/jackpal/androidterm/EmulatorView.java @@ -736,7 +736,7 @@ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, } else { // TODO: add animation man's (non animated) fling mScrollRemainder = 0.0f; - onScroll(e1, e2, 2 * velocityX, -2 * velocityY); + onScroll(e1, e2, 0.1f * velocityX, -0.1f * velocityY); } return true; } From 73590196f54ba2defe96f9f12919dfcf3a6a2b31 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Fri, 18 Nov 2011 02:24:33 -0800 Subject: [PATCH 147/847] Make "Reset terminal" do what it says instead of closing the activity Signed-off-by: Jack Palevich --- src/jackpal/androidterm/Term.java | 5 ++++- src/jackpal/androidterm/session/TermSession.java | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index 32a642b18..e4714c281 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -447,7 +447,10 @@ private void doPreferences() { } private void doResetTerminal() { - restart(); + TermSession session = getCurrentTermSession(); + if (session != null) { + session.reset(); + } } private void doEmailTranscript() { diff --git a/src/jackpal/androidterm/session/TermSession.java b/src/jackpal/androidterm/session/TermSession.java index ff8ac2eca..178ac3001 100644 --- a/src/jackpal/androidterm/session/TermSession.java +++ b/src/jackpal/androidterm/session/TermSession.java @@ -302,6 +302,10 @@ public void updatePrefs(TermSettings settings) { mEmulator.updatePrefs(settings); } + public void reset() { + mEmulator.reset(); + } + public void finish() { Exec.hangupProcessGroup(mProcId); Exec.close(mTermFd); From d702d2c7fed56bd49eb7e6ec94b08621329be911 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Fri, 18 Nov 2011 02:24:40 -0800 Subject: [PATCH 148/847] Add configurable back button behavior Allow the back button to (1) close all windows (the current behavior); (2) close the current window only; (3) close the activity, but leave the terminal sessions running; or (4) send ESC to the terminal. Signed-off-by: Jack Palevich --- res/values/arrays.xml | 15 ++++++ res/values/strings.xml | 5 ++ res/xml/preferences.xml | 9 ++++ src/jackpal/androidterm/EmulatorView.java | 12 ++++- src/jackpal/androidterm/Term.java | 54 ++++++++++++++++++- .../androidterm/util/TermSettings.java | 13 +++++ 6 files changed, 105 insertions(+), 3 deletions(-) diff --git a/res/values/arrays.xml b/res/values/arrays.xml index 35e0834b8..7e2cebc6e 100644 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -97,6 +97,21 @@ 5 + + Closes all terminal windows + Closes this terminal window only + Closes activity, leaving sessions running + Sends ESC to terminal + + + + + 0 + 1 + 2 + 3 + + Jog ball \@ key diff --git a/res/values/strings.xml b/res/values/strings.xml index 4bf613d33..0fbaf1500 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -71,6 +71,10 @@ Keyboard + Back button behavior + Choose what pressing the back button does. + Back button behavior + Control key Choose control key. Control key @@ -98,6 +102,7 @@ 0 10 2 + 0 5 4 0 diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml index fba06fa57..abea6981d 100644 --- a/res/xml/preferences.xml +++ b/res/xml/preferences.xml @@ -84,6 +84,15 @@ + + Date: Fri, 18 Nov 2011 02:24:45 -0800 Subject: [PATCH 149/847] Clean up setting UpdateCallback for a TermSession Remove the unused UpdateCallback argument to the TermSession constructor, and move the setUpdateCallback() into the EmulatorView constructor, eliminating the need for a getUpdateCallback() in EmulatorView. Signed-off-by: Jack Palevich --- src/jackpal/androidterm/EmulatorView.java | 5 +---- src/jackpal/androidterm/Term.java | 4 +--- src/jackpal/androidterm/session/TermSession.java | 3 +-- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/jackpal/androidterm/EmulatorView.java b/src/jackpal/androidterm/EmulatorView.java index ec844e0df..25f6fc74b 100644 --- a/src/jackpal/androidterm/EmulatorView.java +++ b/src/jackpal/androidterm/EmulatorView.java @@ -224,10 +224,6 @@ public void onUpdate() { } }; - public UpdateCallback getUpdateCallback() { - return mUpdateNotify; - } - public EmulatorView(Context context, TermSession session, TermViewFlipper viewFlipper, DisplayMetrics metrics) { super(context); commonConstructor(session, viewFlipper); @@ -590,6 +586,7 @@ private void commonConstructor(TermSession session, TermViewFlipper viewFlipper) setFocusableInTouchMode(true); initialize(session, viewFlipper); + session.setUpdateCallback(mUpdateNotify); } @Override diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index 1048413bf..933b5f95f 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -186,7 +186,7 @@ private TermSession createTermSession() { } } - return new TermSession(mSettings, null, initialCommand); + return new TermSession(mSettings, initialCommand); } private EmulatorView createEmulatorView(TermSession session) { @@ -201,8 +201,6 @@ private EmulatorView createEmulatorView(TermSession session) { ); emulatorView.setLayoutParams(params); - session.setUpdateCallback(emulatorView.getUpdateCallback()); - registerForContextMenu(emulatorView); return emulatorView; diff --git a/src/jackpal/androidterm/session/TermSession.java b/src/jackpal/androidterm/session/TermSession.java index 178ac3001..57fbfb57e 100644 --- a/src/jackpal/androidterm/session/TermSession.java +++ b/src/jackpal/androidterm/session/TermSession.java @@ -86,9 +86,8 @@ public void handleMessage(Message msg) { } }; - public TermSession(TermSettings settings, UpdateCallback notify, String initialCommand) { + public TermSession(TermSettings settings, String initialCommand) { mSettings = settings; - mNotify = notify; int[] processId = new int[1]; From 828cbcfc0e4164aa1bfa92707bf503a2831c56a7 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Fri, 18 Nov 2011 02:24:56 -0800 Subject: [PATCH 150/847] Deal with cases where a session ends without our intervention At the moment, the terminal activity, the window list activity, and the service all assume that terminal sessions only end when they're explicitly closed by an action from our UI (the close buttons in the activities) -- which is currently true, but breaks if sessions end when the shell running in the session exits. To account for the possibility of sessions ending on their own, add callback interfaces to notify when sessions have finished and when the list of sessions changes. Signed-off-by: Jack Palevich --- src/jackpal/androidterm/Term.java | 36 +++++- src/jackpal/androidterm/TermService.java | 17 ++- src/jackpal/androidterm/WindowList.java | 20 +++- .../model/SessionFinishCallback.java | 26 ++++ src/jackpal/androidterm/util/SessionList.java | 113 ++++++++++++++++++ 5 files changed, 198 insertions(+), 14 deletions(-) create mode 100644 src/jackpal/androidterm/model/SessionFinishCallback.java create mode 100644 src/jackpal/androidterm/util/SessionList.java diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index 933b5f95f..eb51aba3b 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -17,7 +17,6 @@ package jackpal.androidterm; import java.io.UnsupportedEncodingException; -import java.util.ArrayList; import android.app.Activity; import android.app.AlertDialog; @@ -51,14 +50,16 @@ import android.widget.FrameLayout; import android.widget.Toast; +import jackpal.androidterm.model.UpdateCallback; import jackpal.androidterm.session.TermSession; +import jackpal.androidterm.util.SessionList; import jackpal.androidterm.util.TermSettings; /** * A terminal emulator activity. */ -public class Term extends Activity { +public class Term extends Activity implements UpdateCallback { /** * The ViewFlipper which holds the collection of EmulatorView widgets. */ @@ -69,7 +70,7 @@ public class Term extends Activity { */ private static final int VIEW_FLIPPER = R.id.view_flipper; - private ArrayList mTermSessions; + private SessionList mTermSessions; private SharedPreferences mPrefs; private TermSettings mSettings; @@ -135,6 +136,7 @@ public void onCreate(Bundle icicle) { private void populateViewFlipper() { if (mTermService != null) { mTermSessions = mTermService.getSessions(); + mTermSessions.addCallback(this); if (mTermSessions.size() == 0) { mTermSessions.add(createTermSession()); @@ -248,6 +250,9 @@ private void updatePrefs() { public void onResume() { super.onResume(); + if (mTermSessions != null) { + mTermSessions.addCallback(this); + } if (mTermSessions != null && mTermSessions.size() < mViewFlipper.getChildCount()) { for (int i = 0; i < mViewFlipper.getChildCount(); ++i) { EmulatorView v = (EmulatorView) mViewFlipper.getChildAt(i); @@ -276,6 +281,9 @@ public void onPause() { super.onPause(); mViewFlipper.pauseCurrentView(); + if (mTermSessions != null) { + mTermSessions.removeCallback(this); + } /* Explicitly close the input method Otherwise, the soft keyboard could cover up whatever activity takes @@ -484,6 +492,28 @@ public boolean onKeyUp(int keyCode, KeyEvent event) { } } + // Called when the list of sessions changes + public void onUpdate() { + SessionList sessions = mTermSessions; + if (sessions == null) { + return; + } + + if (sessions.size() == 0) { + mStopServiceOnFinish = true; + finish(); + } else if (sessions.size() < mViewFlipper.getChildCount()) { + for (int i = 0; i < mViewFlipper.getChildCount(); ++i) { + EmulatorView v = (EmulatorView) mViewFlipper.getChildAt(i); + if (!sessions.contains(v.getTermSession())) { + v.onPause(); + mViewFlipper.removeView(v); + --i; + } + } + } + } + private boolean canPaste() { ClipboardManager clip = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); if (clip.hasText()) { diff --git a/src/jackpal/androidterm/TermService.java b/src/jackpal/androidterm/TermService.java index d721552ce..048758fdd 100644 --- a/src/jackpal/androidterm/TermService.java +++ b/src/jackpal/androidterm/TermService.java @@ -16,8 +16,6 @@ package jackpal.androidterm; -import java.util.ArrayList; - import android.app.Service; import android.os.Binder; import android.os.IBinder; @@ -27,10 +25,13 @@ import android.app.NotificationManager; import android.app.PendingIntent; +import jackpal.androidterm.model.SessionFinishCallback; +import jackpal.androidterm.model.UpdateCallback; import jackpal.androidterm.session.TermSession; import jackpal.androidterm.util.ServiceForegroundCompat; +import jackpal.androidterm.util.SessionList; -public class TermService extends Service +public class TermService extends Service implements SessionFinishCallback { /* Parallels the value of START_STICKY on API Level >= 5 */ private static final int COMPAT_START_STICKY = 1; @@ -38,7 +39,7 @@ public class TermService extends Service private static final int RUNNING_NOTIFICATION = 1; private ServiceForegroundCompat compat; - private ArrayList mTermSessions; + private SessionList mTermSessions; public class TSBinder extends Binder { TermService getService() { @@ -66,7 +67,7 @@ public IBinder onBind(Intent intent) { @Override public void onCreate() { compat = new ServiceForegroundCompat(this); - mTermSessions = new ArrayList(); + mTermSessions = new SessionList(); /* Put the service in the foreground. */ Notification notification = new Notification(R.drawable.ic_stat_service_notification_icon, getText(R.string.service_notify_text), System.currentTimeMillis()); @@ -91,7 +92,11 @@ public void onDestroy() { return; } - public ArrayList getSessions() { + public SessionList getSessions() { return mTermSessions; } + + public void onSessionFinish(TermSession session) { + mTermSessions.remove(session); + } } diff --git a/src/jackpal/androidterm/WindowList.java b/src/jackpal/androidterm/WindowList.java index 447d1bf67..523afad98 100644 --- a/src/jackpal/androidterm/WindowList.java +++ b/src/jackpal/androidterm/WindowList.java @@ -16,8 +16,6 @@ package jackpal.androidterm; -import java.util.ArrayList; - import android.app.ListActivity; import android.content.ComponentName; import android.content.Context; @@ -35,14 +33,16 @@ import android.widget.ListView; import android.widget.TextView; +import jackpal.androidterm.model.UpdateCallback; import jackpal.androidterm.session.TermSession; +import jackpal.androidterm.util.SessionList; public class WindowList extends ListActivity { - private ArrayList sessions; + private SessionList sessions; private WindowListAdapter mWindowListAdapter; private TermService mTermService; - class WindowListAdapter extends BaseAdapter { + class WindowListAdapter extends BaseAdapter implements UpdateCallback { private LayoutInflater inflater = getLayoutInflater(); public int getCount() { @@ -77,6 +77,10 @@ public void onClick(View v) { return child; } + + public void onUpdate() { + notifyDataSetChanged(); + } } /** @@ -146,6 +150,9 @@ protected void onResume() { protected void onPause() { super.onPause(); + if (sessions != null) { + sessions.removeCallback(mWindowListAdapter); + } unbindService(mTSConnection); } @@ -153,10 +160,13 @@ private void populateList() { sessions = mTermService.getSessions(); if (mWindowListAdapter == null) { - setListAdapter(new WindowListAdapter()); + WindowListAdapter adapter = new WindowListAdapter(); + setListAdapter(adapter); + mWindowListAdapter = adapter; } else { mWindowListAdapter.notifyDataSetChanged(); } + sessions.addCallback(mWindowListAdapter); } @Override diff --git a/src/jackpal/androidterm/model/SessionFinishCallback.java b/src/jackpal/androidterm/model/SessionFinishCallback.java new file mode 100644 index 000000000..fe01d0dd6 --- /dev/null +++ b/src/jackpal/androidterm/model/SessionFinishCallback.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jackpal.androidterm.model; + +import jackpal.androidterm.session.TermSession; + +/** + * Callback to be invoked when a TermSession finishes. + */ +public interface SessionFinishCallback { + void onSessionFinish(TermSession session); +} diff --git a/src/jackpal/androidterm/util/SessionList.java b/src/jackpal/androidterm/util/SessionList.java new file mode 100644 index 000000000..a68208db8 --- /dev/null +++ b/src/jackpal/androidterm/util/SessionList.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2011 Steven Luo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jackpal.androidterm.util; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.Collection; + +import jackpal.androidterm.model.UpdateCallback; +import jackpal.androidterm.session.TermSession; + +/** + * An ArrayList of TermSessions which allows users to register callbacks in + * order to be notified when the list is changed. + */ +public class SessionList extends ArrayList +{ + LinkedList callbacks = new LinkedList(); + + public SessionList() { + super(); + } + + public SessionList(int capacity) { + super(capacity); + } + + public void addCallback(UpdateCallback callback) { + callbacks.add(callback); + } + + public boolean removeCallback(UpdateCallback callback) { + return callbacks.remove(callback); + } + + private void notifyChange() { + for (UpdateCallback callback : callbacks) { + callback.onUpdate(); + } + } + + @Override + public boolean add(TermSession object) { + boolean result = super.add(object); + notifyChange(); + return result; + } + + @Override + public void add(int index, TermSession object) { + super.add(index, object); + notifyChange(); + } + + @Override + public boolean addAll(Collection collection) { + boolean result = super.addAll(collection); + notifyChange(); + return result; + } + + @Override + public boolean addAll(int index, Collection collection) { + boolean result = super.addAll(index, collection); + notifyChange(); + return result; + } + + @Override + public void clear() { + super.clear(); + notifyChange(); + } + + @Override + public TermSession remove(int index) { + TermSession object = super.remove(index); + if (object != null) { + notifyChange(); + } + return object; + } + + @Override + public boolean remove(Object object) { + boolean result = super.remove(object); + if (result) { + notifyChange(); + } + return result; + } + + @Override + public TermSession set(int index, TermSession object) { + TermSession old = super.set(index, object); + notifyChange(); + return old; + } +} From 78c3d0b5d2d1f75051d83c540320d2196b4a75b9 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Fri, 18 Nov 2011 02:25:03 -0800 Subject: [PATCH 151/847] Add option to close terminal windows on shell exit Signed-off-by: Jack Palevich --- res/values/strings.xml | 3 +++ res/xml/preferences.xml | 5 +++++ src/jackpal/androidterm/Term.java | 2 +- .../androidterm/session/TermSession.java | 19 +++++++++++++++++-- .../androidterm/util/TermSettings.java | 7 +++++++ 5 files changed, 33 insertions(+), 3 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 0fbaf1500..a2024109c 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -96,6 +96,9 @@ Sent to the shell when it starts. Initial Command + Close window on exit + Whether a window should close when its shell exits. + 0 0 diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml index abea6981d..308ac5fdb 100644 --- a/res/xml/preferences.xml +++ b/res/xml/preferences.xml @@ -137,5 +137,10 @@ android:title="@string/title_initialcommand_preference" android:summary="@string/summary_initialcommand_preference" android:dialogTitle="@string/dialog_title_initialcommand_preference" /> + diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index eb51aba3b..af396715b 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -188,7 +188,7 @@ private TermSession createTermSession() { } } - return new TermSession(mSettings, initialCommand); + return new TermSession(mSettings, mTermService, initialCommand); } private EmulatorView createEmulatorView(TermSession session) { diff --git a/src/jackpal/androidterm/session/TermSession.java b/src/jackpal/androidterm/session/TermSession.java index 57fbfb57e..9c3dca69a 100644 --- a/src/jackpal/androidterm/session/TermSession.java +++ b/src/jackpal/androidterm/session/TermSession.java @@ -33,6 +33,7 @@ import jackpal.androidterm.Exec; import jackpal.androidterm.TermDebug; +import jackpal.androidterm.model.SessionFinishCallback; import jackpal.androidterm.model.UpdateCallback; import jackpal.androidterm.util.ByteQueue; import jackpal.androidterm.util.TermSettings; @@ -45,6 +46,7 @@ public class TermSession { private TermSettings mSettings; private UpdateCallback mNotify; + private SessionFinishCallback mFinishCallback; private int mProcId; private FileDescriptor mTermFd; @@ -72,6 +74,7 @@ public class TermSession { private static final int TRANSCRIPT_ROWS = 10000; private static final int NEW_INPUT = 1; + private static final int PROCESS_EXITED = 2; private boolean mIsRunning = false; private Handler mMsgHandler = new Handler() { @@ -82,12 +85,15 @@ public void handleMessage(Message msg) { } if (msg.what == NEW_INPUT) { readFromProcess(); + } else if (msg.what == PROCESS_EXITED) { + onProcessExit((Integer) msg.obj); } } }; - public TermSession(TermSettings settings, String initialCommand) { + public TermSession(TermSettings settings, SessionFinishCallback finishCallback, String initialCommand) { mSettings = settings; + mFinishCallback = finishCallback; int[] processId = new int[1]; @@ -107,7 +113,7 @@ public void run() { Log.i(TermDebug.LOG_TAG, "waiting for: " + mProcId); int result = Exec.waitFor(mProcId); Log.i(TermDebug.LOG_TAG, "Subprocess exited: " + result); - mMsgHandler.sendEmptyMessage(result); + mMsgHandler.sendMessage(mMsgHandler.obtainMessage(PROCESS_EXITED, result)); } }; watcher.setName("Process watcher"); @@ -305,6 +311,15 @@ public void reset() { mEmulator.reset(); } + private void onProcessExit(int result) { + if (mSettings.closeWindowOnProcessExit()) { + if (mFinishCallback != null) { + mFinishCallback.onSessionFinish(this); + } + finish(); + } + } + public void finish() { Exec.hangupProcessGroup(mProcId); Exec.close(mTermFd); diff --git a/src/jackpal/androidterm/util/TermSettings.java b/src/jackpal/androidterm/util/TermSettings.java index c1fc4cc98..5311cafe9 100644 --- a/src/jackpal/androidterm/util/TermSettings.java +++ b/src/jackpal/androidterm/util/TermSettings.java @@ -37,6 +37,7 @@ public class TermSettings { private String mInitialCommand; private boolean mUTF8ByDefault = false; private int mBackKeyAction = 0; // Default to closing activity + private boolean mCloseOnExit = false; private static final String STATUSBAR_KEY = "statusbar"; private static final String CURSORSTYLE_KEY = "cursorstyle"; @@ -50,6 +51,7 @@ public class TermSettings { private static final String INITIALCOMMAND_KEY = "initialcommand"; private static final String UTF8_KEY = "utf8_by_default"; private static final String BACKACTION_KEY = "backaction"; + private static final String CLOSEONEXIT_KEY = "close_window_on_process_exit"; public static final int WHITE = 0xffffffff; public static final int BLACK = 0xff000000; @@ -113,6 +115,7 @@ public void readPrefs(SharedPreferences prefs) { mInitialCommand = readStringPref(INITIALCOMMAND_KEY, mInitialCommand); mUTF8ByDefault = readBooleanPref(UTF8_KEY, false); mBackKeyAction = readIntPref(BACKACTION_KEY, mBackKeyAction, BACK_KEY_MAX); + mCloseOnExit = readBooleanPref(CLOSEONEXIT_KEY, false); mPrefs = null; // we leak a Context if we hold on to this } @@ -191,4 +194,8 @@ public boolean defaultToUTF8Mode() { public int getBackKeyAction() { return mBackKeyAction; } + + public boolean closeWindowOnProcessExit() { + return mCloseOnExit; + } } From 8e6145ca110950d89a76c5c1b0a9c8e2879b293f Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Fri, 18 Nov 2011 02:25:09 -0800 Subject: [PATCH 152/847] Display a message in the EmulatorView when the shell exits Currently, if the shell exits and the option to close windows on shell exit isn't activated, the user gets no visual cue that the shell has exited -- only an unresponsive session. Instead, take a page from the Mac OS X Terminal's book and display a message that the terminal session has finished. Signed-off-by: Jack Palevich --- res/values/strings.xml | 2 ++ src/jackpal/androidterm/EmulatorView.java | 6 ++++-- .../androidterm/session/TermSession.java | 17 +++++++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index a2024109c..1f7fb74cc 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -41,6 +41,8 @@ Terminal session is running + Terminal session finished + Screen diff --git a/src/jackpal/androidterm/EmulatorView.java b/src/jackpal/androidterm/EmulatorView.java index 25f6fc74b..647bbb242 100644 --- a/src/jackpal/androidterm/EmulatorView.java +++ b/src/jackpal/androidterm/EmulatorView.java @@ -226,7 +226,7 @@ public void onUpdate() { public EmulatorView(Context context, TermSession session, TermViewFlipper viewFlipper, DisplayMetrics metrics) { super(context); - commonConstructor(session, viewFlipper); + commonConstructor(context, session, viewFlipper); setDensity(metrics); } @@ -572,7 +572,7 @@ public boolean getKeypadApplicationMode() { return mEmulator.getKeypadApplicationMode(); } - private void commonConstructor(TermSession session, TermViewFlipper viewFlipper) { + private void commonConstructor(Context context, TermSession session, TermViewFlipper viewFlipper) { mTextRenderer = null; mCursorPaint = new Paint(); mCursorPaint.setARGB(255,128,128,128); @@ -587,6 +587,8 @@ private void commonConstructor(TermSession session, TermViewFlipper viewFlipper) initialize(session, viewFlipper); session.setUpdateCallback(mUpdateNotify); + // XXX We should really be able to fetch this from within TermSession + session.setProcessExitMessage(context.getString(R.string.process_exit_message)); } @Override diff --git a/src/jackpal/androidterm/session/TermSession.java b/src/jackpal/androidterm/session/TermSession.java index 9c3dca69a..5f31fd7b4 100644 --- a/src/jackpal/androidterm/session/TermSession.java +++ b/src/jackpal/androidterm/session/TermSession.java @@ -20,6 +20,7 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; @@ -64,6 +65,8 @@ public class TermSession { private ByteBuffer mWriteByteBuffer; private CharsetEncoder mUTF8Encoder; + private String mProcessExitMessage; + private static final int DEFAULT_COLUMNS = 80; private static final int DEFAULT_ROWS = 24; private static final String DEFAULT_SHELL = "/system/bin/sh -"; @@ -311,12 +314,26 @@ public void reset() { mEmulator.reset(); } + /* XXX We should really get this ourselves from the resource bundle, but + we cannot hold a context */ + public void setProcessExitMessage(String message) { + mProcessExitMessage = message; + } + private void onProcessExit(int result) { if (mSettings.closeWindowOnProcessExit()) { if (mFinishCallback != null) { mFinishCallback.onSessionFinish(this); } finish(); + } else if (mProcessExitMessage != null) { + try { + byte[] msg = ("\r\n[" + mProcessExitMessage + "]").getBytes("UTF-8"); + mEmulator.append(msg, 0, msg.length); + mNotify.onUpdate(); + } catch (UnsupportedEncodingException e) { + // Never happens + } } } From d6d90575bf8856194d2c0377e0288de4b9c7126f Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Fri, 18 Nov 2011 02:25:55 -0800 Subject: [PATCH 153/847] Fix tab behavior when cursor is on tab stop Sending a tab to the emulator when the cursor sits on a tab stop should move the cursor to the next tab stop, not leave the cursor in its current position. Signed-off-by: Jack Palevich --- src/jackpal/androidterm/session/TerminalEmulator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jackpal/androidterm/session/TerminalEmulator.java b/src/jackpal/androidterm/session/TerminalEmulator.java index f1812191f..dc0827914 100644 --- a/src/jackpal/androidterm/session/TerminalEmulator.java +++ b/src/jackpal/androidterm/session/TerminalEmulator.java @@ -628,7 +628,7 @@ private void setAltCharSet(boolean alternateCharSet) { } private int nextTabStop(int cursorCol) { - for (int i = cursorCol; i < mColumns; i++) { + for (int i = cursorCol + 1; i < mColumns; i++) { if (mTabStop[i]) { return i; } From 49021f06086fb16db84044973448c372492e1304 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Fri, 18 Nov 2011 02:26:00 -0800 Subject: [PATCH 154/847] Add a few more escapes to the terminal emulator Implement support for the following escape codes (not supported by a real VT100, but not conflicting with any VT100 escapes either): * Esc [ Z: backwards tab * Esc [ Pn X: erase Pn characters * SGR codes 10/11: exit/enter alternate charset (used when TERM=linux) [ECMA-48 uses this pair of codes for font selection] * SGR codes 3/23: "standout" (reverse video for TERM=screen) [ECMA-48 uses this pair of codes for italics, but this seems to be rarely implemented] With these escapes implemented, we should have the complete set of escapes required to keep termcap/terminfo-using applications happy when TERM=linux or TERM=screen. (We may be missing a few graphic modes/attributes, but nothing that should cause major breakage.) Signed-off-by: Jack Palevich --- .../androidterm/session/TerminalEmulator.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/jackpal/androidterm/session/TerminalEmulator.java b/src/jackpal/androidterm/session/TerminalEmulator.java index dc0827914..2986e5064 100644 --- a/src/jackpal/androidterm/session/TerminalEmulator.java +++ b/src/jackpal/androidterm/session/TerminalEmulator.java @@ -636,6 +636,15 @@ private int nextTabStop(int cursorCol) { return mColumns - 1; } + private int prevTabStop(int cursorCol) { + for (int i = cursorCol - 1; i >= 0; i--) { + if (mTabStop[i]) { + return i; + } + } + return 0; + } + private void doEscPercent(byte b) { switch (b) { case '@': // Esc % @ -- return to ISO 2022 mode @@ -964,6 +973,14 @@ private void doEscLeftSquareBracket(byte b) { unimplementedSequence(b); break; + case 'X': // Erase characters + blockClear(mCursorCol, mCursorRow, getArg0(0)); + break; + + case 'Z': // Back tab + setCursorCol(prevTabStop(mCursorCol)); + break; + case '?': // Esc [ ? -- start of a private mode set continueSequence(ESC_LEFT_SQUARE_BRACKET_QUESTION_MARK); break; @@ -1061,12 +1078,20 @@ private void selectGraphicRendition() { mBackColor = 0; } else if (code == 1) { // bold mForeColor |= 0x8; + } else if (code == 3) { // italics, but rarely used as such; "standout" (inverse colors) with TERM=screen + mInverseColors = true; } else if (code == 4) { // underscore mBackColor |= 0x8; } else if (code == 7) { // inverse mInverseColors = true; + } else if (code == 10) { // exit alt charset (TERM=linux) + setAltCharSet(false); + } else if (code == 11) { // enter alt charset (TERM=linux) + setAltCharSet(true); } else if (code == 22) { // Normal color or intensity, neither bright, bold nor faint mForeColor &= 0x7; + } else if (code == 23) { // not italic, but rarely used as such; clears standout with TERM=screen + mInverseColors = false; } else if (code == 24) { // underline: none mBackColor &= 0x7; } else if (code == 27) { // image: positive From 8f755cfdb4e5909f0be8242ebf2cc152f202c179 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Fri, 18 Nov 2011 02:26:10 -0800 Subject: [PATCH 155/847] Correct color rendition for color schemes other than white on black Applications expect to get black when they ask for a black background, independent of what the default background color actually is; similarly, they expect white when they ask for a white foreground, independent of the default foreground color. We, however, currently substitute the default background color for black and the default foreground color for white in our palette, which leads to incorrect color when the color scheme is anything other than white on black. A full solution to this problem probably requires us to distinguish "default color" from the colors in the palette, which would require additional storage for color information. As a band-aid fix, though, keep track of which colors in the palette are the closest match to the default colors, and substitute those instead of always substituting for black and white. Signed-off-by: Jack Palevich --- src/jackpal/androidterm/EmulatorView.java | 35 ++++++++++++------- .../androidterm/session/TermSession.java | 6 +++- .../androidterm/session/TerminalEmulator.java | 20 +++++++---- .../androidterm/session/TranscriptScreen.java | 4 +++ .../androidterm/util/TermSettings.java | 2 +- 5 files changed, 46 insertions(+), 21 deletions(-) diff --git a/src/jackpal/androidterm/EmulatorView.java b/src/jackpal/androidterm/EmulatorView.java index 647bbb242..50fdee592 100644 --- a/src/jackpal/androidterm/EmulatorView.java +++ b/src/jackpal/androidterm/EmulatorView.java @@ -109,11 +109,13 @@ public class EmulatorView extends View implements GestureDetector.OnGestureListe * Foreground color. */ private int mForeground; + private int mForegroundIndex; /** * Background color. */ private int mBackground; + private int mBackgroundIndex; /** * Used to paint the cursor @@ -260,8 +262,10 @@ public void updatePrefs(TermSettings settings) { public void setColors() { int[] scheme = mSettings.getColorScheme(); - mForeground = scheme[0]; - mBackground = scheme[1]; + mForegroundIndex = scheme[0]; + mForeground = scheme[1]; + mBackgroundIndex = scheme[2]; + mBackground = scheme[3]; updateText(); } @@ -875,12 +879,14 @@ private boolean isSystemKey(int keyCode, KeyEvent event) { private void updateText() { if (mTextSize > 0) { - mTextRenderer = new PaintRenderer(mTextSize, mForeground, - mBackground); + mTextRenderer = new PaintRenderer(mTextSize, + mForegroundIndex, mForeground, + mBackgroundIndex, mBackground); } else { mTextRenderer = new Bitmap4x8FontRenderer(getResources(), - mForeground, mBackground); + mForegroundIndex, mForeground, + mBackgroundIndex, mBackground); } mBackgroundPaint.setColor(mBackground); mCharacterWidth = mTextRenderer.getCharacterWidth(); @@ -1023,10 +1029,10 @@ abstract class BaseTextRenderer implements TextRenderer { }; protected final static int mCursorPaint = 0xff808080; - public BaseTextRenderer(int forePaintColor, int backPaintColor) { - mForePaint[7] = forePaintColor; - mBackPaint[0] = backPaintColor; - + public BaseTextRenderer(int forePaintIndex, int forePaintColor, + int backPaintIndex, int backPaintColor) { + mForePaint[forePaintIndex] = forePaintColor; + mBackPaint[backPaintIndex] = backPaintColor; } } @@ -1041,8 +1047,9 @@ class Bitmap4x8FontRenderer extends BaseTextRenderer { private static final float BYTE_SCALE = 1.0f / 255.0f; public Bitmap4x8FontRenderer(Resources resources, - int forePaintColor, int backPaintColor) { - super(forePaintColor, backPaintColor); + int forePaintIndex, int forePaintColor, + int backPaintIndex, int backPaintColor) { + super(forePaintIndex, forePaintColor, backPaintIndex, backPaintColor); mFont = BitmapFactory.decodeResource(resources, R.drawable.atari_small); mPaint = new Paint(); @@ -1110,8 +1117,10 @@ private void setColorMatrix(int foreColor, int backColor) { } class PaintRenderer extends BaseTextRenderer { - public PaintRenderer(int fontSize, int forePaintColor, int backPaintColor) { - super(forePaintColor, backPaintColor); + public PaintRenderer(int fontSize, + int forePaintIndex, int forePaintColor, + int backPaintIndex, int backPaintColor) { + super(forePaintIndex, forePaintColor, backPaintIndex, backPaintColor); mTextPaint = new Paint(); mTextPaint.setTypeface(Typeface.MONOSPACE); mTextPaint.setAntiAlias(true); diff --git a/src/jackpal/androidterm/session/TermSession.java b/src/jackpal/androidterm/session/TermSession.java index 5f31fd7b4..16aaecadb 100644 --- a/src/jackpal/androidterm/session/TermSession.java +++ b/src/jackpal/androidterm/session/TermSession.java @@ -105,7 +105,8 @@ public TermSession(TermSettings settings, SessionFinishCallback finishCallback, mTermOut = new FileOutputStream(mTermFd); mTermIn = new FileInputStream(mTermFd); - mTranscriptScreen = new TranscriptScreen(DEFAULT_COLUMNS, TRANSCRIPT_ROWS, DEFAULT_ROWS, 0, 7); + int[] colorScheme = settings.getColorScheme(); + mTranscriptScreen = new TranscriptScreen(DEFAULT_COLUMNS, TRANSCRIPT_ROWS, DEFAULT_ROWS, colorScheme[0], colorScheme[2]); mEmulator = new TerminalEmulator(settings, mTranscriptScreen, DEFAULT_COLUMNS, DEFAULT_ROWS, mTermOut); mIsRunning = true; @@ -308,6 +309,9 @@ private void readFromProcess() { public void updatePrefs(TermSettings settings) { mSettings = settings; mEmulator.updatePrefs(settings); + + int[] colorScheme = settings.getColorScheme(); + mTranscriptScreen.setDefaultColors(colorScheme[0], colorScheme[2]); } public void reset() { diff --git a/src/jackpal/androidterm/session/TerminalEmulator.java b/src/jackpal/androidterm/session/TerminalEmulator.java index 2986e5064..cc5f20532 100644 --- a/src/jackpal/androidterm/session/TerminalEmulator.java +++ b/src/jackpal/androidterm/session/TerminalEmulator.java @@ -249,11 +249,13 @@ public class TerminalEmulator { * Foreground color, 0..7, mask with 8 for bold */ private int mForeColor; + private int mDefaultForeColor; /** * Background color, 0..7, mask with 8 for underline */ private int mBackColor; + private int mDefaultBackColor; private boolean mInverseColors; @@ -1074,8 +1076,8 @@ private void selectGraphicRendition() { if (code == 0) { // reset mInverseColors = false; - mForeColor = 7; - mBackColor = 0; + mForeColor = mDefaultForeColor; + mBackColor = mDefaultBackColor; } else if (code == 1) { // bold mForeColor |= 0x8; } else if (code == 3) { // italics, but rarely used as such; "standout" (inverse colors) with TERM=screen @@ -1099,12 +1101,12 @@ private void selectGraphicRendition() { } else if (code >= 30 && code <= 37) { // foreground color mForeColor = (mForeColor & 0x8) | (code - 30); } else if (code == 39) { // set default text color - mForeColor = 7; + mForeColor = mDefaultForeColor; mBackColor = mBackColor & 0x7; } else if (code >= 40 && code <= 47) { // background color mBackColor = (mBackColor & 0x8) | (code - 40); } else if (code == 49) { // set default background color - mBackColor = mBackColor & 0x8; // color 0, but preserve underscore. + mBackColor = mDefaultBackColor | (mBackColor & 0x8); // preserve underscore. } else { if (TermDebug.LOG_UNKNOWN_ESCAPE_SEQUENCES) { Log.w(TermDebug.LOG_TAG, String.format("SGR unknown code %d", code)); @@ -1465,8 +1467,11 @@ public void reset() { mTopMargin = 0; mBottomMargin = mRows; mAboutToAutoWrap = false; - mForeColor = 7; - mBackColor = 0; + int[] colorScheme = mTermSettings.getColorScheme(); + mDefaultForeColor = colorScheme[0]; + mDefaultBackColor = colorScheme[2]; + mForeColor = colorScheme[0]; + mBackColor = colorScheme[2]; mInverseColors = false; mbKeypadApplicationMode = false; mAlternateCharSet = false; @@ -1492,6 +1497,9 @@ public void updatePrefs(TermSettings settings) { } mUTF8Mode = newUTF8Mode; } + int[] colorScheme = mTermSettings.getColorScheme(); + mDefaultForeColor = colorScheme[0]; + mDefaultBackColor = colorScheme[2]; } public String getSelectedText(int x1, int y1, int x2, int y2) { diff --git a/src/jackpal/androidterm/session/TranscriptScreen.java b/src/jackpal/androidterm/session/TranscriptScreen.java index 1d3cb600a..d10967663 100644 --- a/src/jackpal/androidterm/session/TranscriptScreen.java +++ b/src/jackpal/androidterm/session/TranscriptScreen.java @@ -74,6 +74,10 @@ private void init(int columns, int totalRows, int screenRows, int foreColor, int mData.blockSet(0, 0, mColumns, mScreenRows, ' ', foreColor, backColor); } + public void setDefaultColors(int foreColor, int backColor) { + mData.setDefaultColors(foreColor, backColor); + } + public void finish() { /* * The Android InputMethodService will sometimes hold a reference to diff --git a/src/jackpal/androidterm/util/TermSettings.java b/src/jackpal/androidterm/util/TermSettings.java index 5311cafe9..ccdf422c8 100644 --- a/src/jackpal/androidterm/util/TermSettings.java +++ b/src/jackpal/androidterm/util/TermSettings.java @@ -60,7 +60,7 @@ public class TermSettings { public static final int AMBER = 0xffffb651; public static final int RED = 0xffff0113; - public static final int[][] COLOR_SCHEMES = {{BLACK, WHITE}, {WHITE, BLACK}, {WHITE, BLUE}, {GREEN, BLACK}, {AMBER, BLACK}, {RED, BLACK}}; + public static final int[][] COLOR_SCHEMES = {{0, BLACK, 7, WHITE}, {7, WHITE, 0, BLACK}, {7, WHITE, 4, BLUE}, {2, GREEN, 0, BLACK}, {3, AMBER, 0, BLACK}, {1, RED, 0, BLACK}}; /** An integer not in the range of real key codes. */ public static final int KEYCODE_NONE = -1; From e48fb0802d50bdeb35d158ccb8a33357fcb7d5b5 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Fri, 18 Nov 2011 02:26:15 -0800 Subject: [PATCH 156/847] Support the VT100 special graphics character set The VT100 special graphics character set is used as the alternate (G1) character set by many full-screen applications which want to draw lines and boxes on screen; these applications currently show lots of "q"s and "x"s where boxes should be. To implement this character set, we provide a mapping between the ASCII characters sent by the application in this character set and Unicode codepoints for the expected display characters, and use it when the G1 character set is selected (via the existing support for the SI and SO control characters). Signed-off-by: Jack Palevich --- .../androidterm/session/TerminalEmulator.java | 54 ++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/src/jackpal/androidterm/session/TerminalEmulator.java b/src/jackpal/androidterm/session/TerminalEmulator.java index cc5f20532..138c1dcd7 100644 --- a/src/jackpal/androidterm/session/TerminalEmulator.java +++ b/src/jackpal/androidterm/session/TerminalEmulator.java @@ -263,6 +263,54 @@ public class TerminalEmulator { private boolean mAlternateCharSet; + /** + * Special graphics character set + */ + private static final char[] mSpecialGraphicsCharMap = new char[128]; + static { + for (char i = 0; i < 128; ++i) { + mSpecialGraphicsCharMap[i] = i; + } + mSpecialGraphicsCharMap['_'] = ' '; // Blank + mSpecialGraphicsCharMap['b'] = 0x2409; // Tab + mSpecialGraphicsCharMap['c'] = 0x240C; // Form feed + mSpecialGraphicsCharMap['d'] = 0x240D; // Carriage return + mSpecialGraphicsCharMap['e'] = 0x240A; // Line feed + mSpecialGraphicsCharMap['h'] = 0x2424; // New line + mSpecialGraphicsCharMap['i'] = 0x240B; // Vertical tab/"lantern" + mSpecialGraphicsCharMap['}'] = 0x00A3; // Pound sterling symbol + mSpecialGraphicsCharMap['f'] = 0x00B0; // Degree symbol + mSpecialGraphicsCharMap['`'] = 0x2B25; // Diamond + mSpecialGraphicsCharMap['~'] = 0x2022; // Bullet point + mSpecialGraphicsCharMap['y'] = 0x2264; // Less-than-or-equals sign (<=) + mSpecialGraphicsCharMap['|'] = 0x2260; // Not equals sign (!=) + mSpecialGraphicsCharMap['z'] = 0x2265; // Greater-than-or-equals sign (>=) + mSpecialGraphicsCharMap['g'] = 0x00B1; // Plus-or-minus sign (+/-) + mSpecialGraphicsCharMap['{'] = 0x03C0; // Lowercase Greek letter pi + mSpecialGraphicsCharMap['.'] = 0x25BC; // Down arrow + mSpecialGraphicsCharMap[','] = 0x25C0; // Left arrow + mSpecialGraphicsCharMap['+'] = 0x25B6; // Right arrow + mSpecialGraphicsCharMap['-'] = 0x25B2; // Up arrow + mSpecialGraphicsCharMap['h'] = '#'; // Board of squares + mSpecialGraphicsCharMap['a'] = 0x2592; // Checkerboard + mSpecialGraphicsCharMap['0'] = 0x2588; // Solid block + mSpecialGraphicsCharMap['q'] = 0x2500; // Horizontal line (box drawing) + mSpecialGraphicsCharMap['x'] = 0x2502; // Vertical line (box drawing) + mSpecialGraphicsCharMap['m'] = 0x2514; // Lower left hand corner (box drawing) + mSpecialGraphicsCharMap['j'] = 0x2518; // Lower right hand corner (box drawing) + mSpecialGraphicsCharMap['l'] = 0x250C; // Upper left hand corner (box drawing) + mSpecialGraphicsCharMap['k'] = 0x2510; // Upper right hand corner (box drawing) + mSpecialGraphicsCharMap['w'] = 0x252C; // T pointing downwards (box drawing) + mSpecialGraphicsCharMap['u'] = 0x2524; // T pointing leftwards (box drawing) + mSpecialGraphicsCharMap['t'] = 0x251C; // T pointing rightwards (box drawing) + mSpecialGraphicsCharMap['v'] = 0x2534; // T pointing upwards (box drawing) + mSpecialGraphicsCharMap['n'] = 0x253C; // Large plus/lines crossing (box drawing) + mSpecialGraphicsCharMap['o'] = 0x23BA; // Horizontal scanline 1 + mSpecialGraphicsCharMap['p'] = 0x23BB; // Horizontal scanline 3 + mSpecialGraphicsCharMap['r'] = 0x23BC; // Horizontal scanline 7 + mSpecialGraphicsCharMap['s'] = 0x23BD; // Horizontal scanline 9 + } + /** * Used for moving selection up along with the scrolling text */ @@ -1386,7 +1434,11 @@ private void emit(int c) { } private void emit(byte b) { - emit((int) b); + if (mAlternateCharSet && b < 128) { + emit((int) mSpecialGraphicsCharMap[b]); + } else { + emit((int) b); + } } /** From cf088c494f2c5b846bb30d9b899ca698997d8a72 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Fri, 18 Nov 2011 02:26:21 -0800 Subject: [PATCH 157/847] Make Exec.createSubprocess() handle environment variables and unlimited args Add the ability to modify the subprocess's environment by passing in an array of environment variable strings suitable for passing to putenv() ("VAR=value"). While we're at it, lift the restriction on the number of arguments passed to the subprocess by using an array instead of a fixed number of String arguments to hold command line arguments. This change breaks the ABI for the JNI library, so bump the library ABI version (to 3) to avoid any potential problems with external copies of the library. Signed-off-by: Jack Palevich --- Android.mk | 2 +- jni/Android.mk | 2 +- jni/termExec.cpp | 99 +++++++++++++++---- src/jackpal/androidterm/Exec.java | 9 +- .../androidterm/session/TermSession.java | 15 +-- 5 files changed, 90 insertions(+), 37 deletions(-) diff --git a/Android.mk b/Android.mk index f3227ae78..6f95cb43f 100644 --- a/Android.mk +++ b/Android.mk @@ -30,7 +30,7 @@ LOCAL_PACKAGE_NAME := AndroidTerm LOCAL_MODULE_TAGS := optional -LOCAL_JNI_SHARED_LIBRARIES := libjackpal-androidterm2 +LOCAL_JNI_SHARED_LIBRARIES := libjackpal-androidterm3 include $(BUILD_PACKAGE) diff --git a/jni/Android.mk b/jni/Android.mk index 7e2b62571..da25a8f36 100644 --- a/jni/Android.mk +++ b/jni/Android.mk @@ -21,7 +21,7 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) # This is the target being built. -LOCAL_MODULE:= libjackpal-androidterm2 +LOCAL_MODULE:= libjackpal-androidterm3 # All of the source files that we will compile. LOCAL_SRC_FILES:= \ diff --git a/jni/termExec.cpp b/jni/termExec.cpp index 3d87954db..c7fcad1a6 100644 --- a/jni/termExec.cpp +++ b/jni/termExec.cpp @@ -72,6 +72,9 @@ class String8 { free(mString); } mString = (char*) malloc(numChars + 1); + if (!mString) { + return; + } for (size_t i = 0; i < numChars; i++) { mString[i] = (char) o[i]; } @@ -85,8 +88,17 @@ class String8 { char* mString; }; -static int create_subprocess(const char *cmd, const char *arg0, const char *arg1, - int* pProcessId) +static int throwOutOfMemoryError(JNIEnv *env, const char *message) +{ + jclass exClass; + const char *className = "java/lang/OutOfMemoryError"; + + exClass = env->FindClass(className); + return env->ThrowNew(exClass, message); +} + +static int create_subprocess(const char *cmd, + char *const argv[], char *const envp[], int* pProcessId) { char *devname; int ptm; @@ -125,7 +137,13 @@ static int create_subprocess(const char *cmd, const char *arg0, const char *arg1 dup2(pts, 1); dup2(pts, 2); - execl(cmd, cmd, arg0, arg1, NULL); + if (envp) { + for (; *envp; ++envp) { + putenv(*envp); + } + } + + execv(cmd, argv); exit(-1); } else { *pProcessId = (int) pid; @@ -135,7 +153,8 @@ static int create_subprocess(const char *cmd, const char *arg0, const char *arg1 static jobject android_os_Exec_createSubProcess(JNIEnv *env, jobject clazz, - jstring cmd, jstring arg0, jstring arg1, jintArray processIdArray) + jstring cmd, jobjectArray args, jobjectArray envVars, + jintArray processIdArray) { const jchar* str = cmd ? env->GetStringCritical(cmd, 0) : 0; String8 cmd_8; @@ -144,26 +163,66 @@ static jobject android_os_Exec_createSubProcess(JNIEnv *env, jobject clazz, env->ReleaseStringCritical(cmd, str); } - str = arg0 ? env->GetStringCritical(arg0, 0) : 0; - const char* arg0Str = 0; - String8 arg0_8; - if (str) { - arg0_8.set(str, env->GetStringLength(arg0)); - env->ReleaseStringCritical(arg0, str); - arg0Str = arg0_8.string(); + jsize size = args ? env->GetArrayLength(args) : 0; + char **argv = NULL; + String8 tmp_8; + if (size > 0) { + argv = (char **)malloc((size+1)*sizeof(char *)); + if (!argv) { + throwOutOfMemoryError(env, "Couldn't allocate argv array"); + return NULL; + } + for (int i = 0; i < size; ++i) { + jstring arg = reinterpret_cast(env->GetObjectArrayElement(args, i)); + str = env->GetStringCritical(arg, 0); + if (!str) { + throwOutOfMemoryError(env, "Couldn't get argument from array"); + return NULL; + } + tmp_8.set(str, env->GetStringLength(arg)); + env->ReleaseStringCritical(arg, str); + argv[i] = strdup(tmp_8.string()); + } + argv[size] = NULL; } - str = arg1 ? env->GetStringCritical(arg1, 0) : 0; - const char* arg1Str = 0; - String8 arg1_8; - if (str) { - arg1_8.set(str, env->GetStringLength(arg1)); - env->ReleaseStringCritical(arg1, str); - arg1Str = arg1_8.string(); + size = envVars ? env->GetArrayLength(envVars) : 0; + char **envp = NULL; + if (size > 0) { + envp = (char **)malloc((size+1)*sizeof(char *)); + if (!envp) { + throwOutOfMemoryError(env, "Couldn't allocate envp array"); + return NULL; + } + for (int i = 0; i < size; ++i) { + jstring var = reinterpret_cast(env->GetObjectArrayElement(envVars, i)); + str = env->GetStringCritical(var, 0); + if (!str) { + throwOutOfMemoryError(env, "Couldn't get env var from array"); + return NULL; + } + tmp_8.set(str, env->GetStringLength(var)); + env->ReleaseStringCritical(var, str); + envp[i] = strdup(tmp_8.string()); + } + envp[size] = NULL; } int procId; - int ptm = create_subprocess(cmd_8.string(), arg0Str, arg1Str, &procId); + int ptm = create_subprocess(cmd_8.string(), argv, envp, &procId); + + if (argv) { + for (char **tmp = argv; *tmp; ++tmp) { + free(*tmp); + } + free(argv); + } + if (envp) { + for (char **tmp = envp; *tmp; ++tmp) { + free(*tmp); + } + free(envp); + } if (processIdArray) { int procIdLen = env->GetArrayLength(processIdArray); @@ -278,7 +337,7 @@ static int register_FileDescriptor(JNIEnv *env) static const char *classPathName = "jackpal/androidterm/Exec"; static JNINativeMethod method_table[] = { - { "createSubprocess", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[I)Ljava/io/FileDescriptor;", + { "createSubprocess", "(Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;[I)Ljava/io/FileDescriptor;", (void*) android_os_Exec_createSubProcess }, { "setPtyWindowSize", "(Ljava/io/FileDescriptor;IIII)V", (void*) android_os_Exec_setPtyWindowSize}, diff --git a/src/jackpal/androidterm/Exec.java b/src/jackpal/androidterm/Exec.java index 3f947611a..b2f342fcc 100644 --- a/src/jackpal/androidterm/Exec.java +++ b/src/jackpal/androidterm/Exec.java @@ -30,7 +30,7 @@ public class Exec { static { - System.loadLibrary("jackpal-androidterm2"); + System.loadLibrary("jackpal-androidterm3"); } /** @@ -41,15 +41,16 @@ public class Exec * file descriptor. * * @param cmd The command to execute - * @param arg0 The first argument to the command, may be null - * @param arg1 the second argument to the command, may be null + * @param args An array of arguments to the command + * @param envVars An array of strings of the form "VAR=value" to be added + * to the environment of the process * @param processId A one-element array to which the process ID of the * started process will be written. * @return the file descriptor of the started process. * */ public static native FileDescriptor createSubprocess( - String cmd, String arg0, String arg1, int[] processId); + String cmd, String[] args, String[] envVars, int[] processId); /** * Set the widow size for a given pty. Allows programs diff --git a/src/jackpal/androidterm/session/TermSession.java b/src/jackpal/androidterm/session/TermSession.java index 16aaecadb..062dcd738 100644 --- a/src/jackpal/androidterm/session/TermSession.java +++ b/src/jackpal/androidterm/session/TermSession.java @@ -202,17 +202,10 @@ private void createSubprocess(int[] processId) { if (shell == null || shell.equals("")) { shell = DEFAULT_SHELL; } - ArrayList args = parse(shell); - String arg0 = args.get(0); - String arg1 = null; - String arg2 = null; - if (args.size() >= 2) { - arg1 = args.get(1); - } - if (args.size() >= 3) { - arg2 = args.get(2); - } - mTermFd = Exec.createSubprocess(arg0, arg1, arg2, processId); + ArrayList argList = parse(shell); + String arg0 = argList.get(0); + String[] args = argList.toArray(new String[1]); + mTermFd = Exec.createSubprocess(arg0, args, null, processId); } private ArrayList parse(String cmd) { From 4d81c669a71d7a242f79349448e16fb348c4e6fb Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Fri, 18 Nov 2011 02:26:26 -0800 Subject: [PATCH 158/847] Set TERM in process environment and provide preference to choose value In ordinary Unix (non-Android) environments, the behavior of programs that use advanced terminal capabilities depends on the value of TERM in the environment. Currently, we don't set TERM at all, which leads most of those programs to assume a dumb terminal and behave accordingly. Instead, set TERM in the started process's environment, and provide the user a preference to choose the value of TERM. The current default of "vt100" is the safest, as it's understood on every system since near the beginning of time, but does not expose capabilities such as colors or Home/End keys. The other options provided are "screen", which is the best match for our currently implemented capabilities and reasonably widespread, and "linux". Signed-off-by: Jack Palevich --- res/values/arrays.xml | 7 +++++++ res/values/strings.xml | 5 +++++ res/xml/preferences.xml | 8 ++++++++ src/jackpal/androidterm/session/TermSession.java | 11 ++++++++++- src/jackpal/androidterm/util/TermSettings.java | 7 +++++++ 5 files changed, 37 insertions(+), 1 deletion(-) diff --git a/res/values/arrays.xml b/res/values/arrays.xml index 7e2cebc6e..e1f0a9828 100644 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -192,4 +192,11 @@ Camera None + + + + vt100 + screen + linux + diff --git a/res/values/strings.xml b/res/values/strings.xml index 1f7fb74cc..bbc59b77e 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -98,6 +98,10 @@ Sent to the shell when it starts. Initial Command + Terminal type + What terminal type to report to the shell. + Terminal type + Close window on exit Whether a window should close when its shell exits. @@ -113,6 +117,7 @@ 0 /system/bin/sh - export PATH=/data/local/bin:$PATH + vt100 Control and Function Keys + Terminal Emulator Préférences - RàZ terminal + Nlle fenêtre + Fermer fenêtre + Fenêtre + Fenêtre Prec. + Fenêtre Suiv. + Terminal par défaut Envoyer par e-mail Touches spéciales Afficher/Masquer Clavier + Activer WakeLock Désactiver WakeLock Activer WifiLock @@ -31,10 +38,14 @@ Selectionner le texte Tout copier Coller + + Fenêtre %1$d - Une session terminal est en cours d\'exécution + Session terminal en cours d\'exécution + + Session terminal terminée - + Écran Barre de statut @@ -50,6 +61,9 @@ Clignotement curseur Texte + + UTF-8 par défaut + Activer le mode UTF-8 par défaut Taille police Choisir la taille de la police de caractères en points @@ -79,6 +93,6 @@ Shell Commande initiale - Envoyée au shell au démarrage + Choisir une commande au démarrage du shell Commande initiale From f3cb8698a1734b0c90cf91dcd522957bb1a4083f Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Sun, 20 Nov 2011 23:26:17 -0800 Subject: [PATCH 160/847] Preserve bold when setting default foreground color (SGR code 39) This matches the Linux console terminal emulator's behavior and (apparently) the expectations of full-screen programs. Signed-off-by: Jack Palevich --- src/jackpal/androidterm/session/TerminalEmulator.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/jackpal/androidterm/session/TerminalEmulator.java b/src/jackpal/androidterm/session/TerminalEmulator.java index 138c1dcd7..35c9c731d 100644 --- a/src/jackpal/androidterm/session/TerminalEmulator.java +++ b/src/jackpal/androidterm/session/TerminalEmulator.java @@ -1149,8 +1149,8 @@ private void selectGraphicRendition() { } else if (code >= 30 && code <= 37) { // foreground color mForeColor = (mForeColor & 0x8) | (code - 30); } else if (code == 39) { // set default text color - mForeColor = mDefaultForeColor; - mBackColor = mBackColor & 0x7; + mForeColor = mDefaultForeColor | (mForeColor & 0x8); // preserve bold + mBackColor = mBackColor & 0x7; // no underline } else if (code >= 40 && code <= 47) { // background color mBackColor = (mBackColor & 0x8) | (code - 40); } else if (code == 49) { // set default background color From 099b500e28317f39e2b5d0d03e06ef75980e327b Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Sun, 20 Nov 2011 23:58:56 -0800 Subject: [PATCH 161/847] Make sure onResume() is called for active EmulatorView in populateViewFlipper() Otherwise, the screen size check handler isn't installed in the EmulatorView, and the screen won't resize properly until something else causes its onResume() to be called. Signed-off-by: Jack Palevich --- src/jackpal/androidterm/Term.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index af396715b..f23f22240 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -148,6 +148,7 @@ private void populateViewFlipper() { } updatePrefs(); + mViewFlipper.resumeCurrentView(); } } From 24ae987a8aff670fc638b932d1dfd89ffceb7fc6 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Mon, 21 Nov 2011 11:57:09 -0800 Subject: [PATCH 162/847] Tweak default preferences Default back key preference is to send ESC Default TERM setting is screen Close Window on process exit defaults to true. Linux term users expect this, even though it can lead to accidental data loss. --- res/values/strings.xml | 8 ++++---- res/xml/preferences.xml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index bbc59b77e..d8a22162f 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -111,14 +111,14 @@ 0 10 2 - 0 + 3 5 4 0 /system/bin/sh - export PATH=/data/local/bin:$PATH - vt100 - + screen + Control and Function Keys @@ -128,5 +128,5 @@ actual function key key name. --> FNKEY 1..9 : F1-F9\nFNKEY 0 : F10\nFNKEY W : Up\nFNKEY A : Left\nFNKEY S : Down\nFNKEY D : Right\nFNKEY P : PageUp\nFNKEY N : PageDown\nFNKEY T : Tab\nFNKEY L : | (pipe)\nFNKEY U : _ (underscore)\nFNKEY E : Control-[ (ESC)\nFNKEY X : Delete\nFNKEY I : Insert\nFNKEY H : Home\nFNKEY F : End\nFNKEY . : Control-\\\n No function key set. - + diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml index 0a32a97e8..787217c0d 100644 --- a/res/xml/preferences.xml +++ b/res/xml/preferences.xml @@ -147,7 +147,7 @@ android:dialogTitle="@string/dialog_title_termtype_preference" /> From 826f76507cfc536a7de64fb560c39224acadd656 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Mon, 21 Nov 2011 12:03:39 -0800 Subject: [PATCH 163/847] Allow the Back button to be configured to send "TAB" --- res/values/arrays.xml | 6 ++++-- src/jackpal/androidterm/EmulatorView.java | 20 +++++++++++++++---- .../androidterm/util/TermSettings.java | 16 +++++++++++++-- 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/res/values/arrays.xml b/res/values/arrays.xml index e1f0a9828..532237758 100644 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -102,6 +102,7 @@ Closes this terminal window only Closes activity, leaving sessions running Sends ESC to terminal + Sends TAB to terminal @@ -110,6 +111,7 @@ 1 2 3 + 4 @@ -168,7 +170,7 @@ 0 1 - + Ball @@ -180,7 +182,7 @@ Camera None - + Ball diff --git a/src/jackpal/androidterm/EmulatorView.java b/src/jackpal/androidterm/EmulatorView.java index 50fdee592..9b632ca2f 100644 --- a/src/jackpal/androidterm/EmulatorView.java +++ b/src/jackpal/androidterm/EmulatorView.java @@ -258,6 +258,7 @@ public void updatePrefs(TermSettings settings) { setCursorStyle(mSettings.getCursorStyle(), mSettings.getCursorBlink()); setUseCookedIME(mSettings.useCookedIME()); setColors(); + mKeyListener.setBackKeyCharacter(settings.getBackKeyCharacter()); } public void setColors() { @@ -814,8 +815,8 @@ public boolean onKeyDown(int keyCode, KeyEvent event) { } else if (handleFnKey(keyCode, true)) { return true; } else if (isSystemKey(keyCode, event)) { - // Don't intercept the system keys - if (keyCode != KeyEvent.KEYCODE_BACK || mSettings.getBackKeyAction() != TermSettings.BACK_KEY_SENDS_ESC) { + if (! isInterceptedSystemKey(keyCode) ) { + // Don't intercept the system keys return super.onKeyDown(keyCode, event); } } @@ -830,6 +831,11 @@ public boolean onKeyDown(int keyCode, KeyEvent event) { return true; } + /** Do we want to intercept this system key? */ + private boolean isInterceptedSystemKey(int keyCode) { + return keyCode == KeyEvent.KEYCODE_BACK && mSettings.backKeySendsCharacter(); + } + @Override public boolean onKeyUp(int keyCode, KeyEvent event) { if (LOG_KEY_EVENTS) { @@ -841,7 +847,7 @@ public boolean onKeyUp(int keyCode, KeyEvent event) { return true; } else if (isSystemKey(keyCode, event)) { // Don't intercept the system keys - if (keyCode != KeyEvent.KEYCODE_BACK || mSettings.getBackKeyAction() != TermSettings.BACK_KEY_SENDS_ESC) { + if ( ! isInterceptedSystemKey(keyCode) ) { return super.onKeyUp(keyCode, event); } } @@ -1830,6 +1836,8 @@ public boolean isActive() { private TermSession mTermSession; + private int mBackKeyCode; + // Map keycodes out of (above) the Unicode code point space. static public final int KEYCODE_OFFSET = 0xA00000; @@ -1842,6 +1850,10 @@ public TermKeyListener(TermSession termSession) { initKeyCodes(); } + public void setBackKeyCharacter(int code) { + mBackKeyCode = code; + } + public void handleControlKey(boolean down) { if (down) { mControlKey.onPress(); @@ -1968,7 +1980,7 @@ public void keyDown(int keyCode, KeyEvent event, boolean appMode) throws IOExcep break; case KeyEvent.KEYCODE_BACK: - result = 27; // ESC + result = mBackKeyCode; break; default: { diff --git a/src/jackpal/androidterm/util/TermSettings.java b/src/jackpal/androidterm/util/TermSettings.java index 33a94cb2b..ea2fd868d 100644 --- a/src/jackpal/androidterm/util/TermSettings.java +++ b/src/jackpal/androidterm/util/TermSettings.java @@ -36,7 +36,7 @@ public class TermSettings { private String mShell; private String mInitialCommand; private boolean mUTF8ByDefault = false; - private int mBackKeyAction = 0; // Default to closing activity + private int mBackKeyAction = BACK_KEY_SENDS_ESC; private String mTermType; private boolean mCloseOnExit = false; @@ -95,7 +95,8 @@ public class TermSettings { public static final int BACK_KEY_CLOSES_WINDOW = 1; public static final int BACK_KEY_CLOSES_ACTIVITY = 2; public static final int BACK_KEY_SENDS_ESC = 3; - private static final int BACK_KEY_MAX = 3; + public static final int BACK_KEY_SENDS_TAB = 4; + private static final int BACK_KEY_MAX = 4; public TermSettings(SharedPreferences prefs) { readPrefs(prefs); @@ -198,6 +199,17 @@ public int getBackKeyAction() { return mBackKeyAction; } + public boolean backKeySendsCharacter() { + return mBackKeyAction >= BACK_KEY_SENDS_ESC; + } + public int getBackKeyCharacter() { + switch (mBackKeyAction) { + case BACK_KEY_SENDS_ESC: return 27; + case BACK_KEY_SENDS_TAB: return 9; + default: return 0; + } + } + public String getTermType() { return mTermType; } From a39027d7996dca02afc557ca17063f75f8354bde Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Mon, 21 Nov 2011 16:56:26 -0800 Subject: [PATCH 164/847] Finish changing default preferences Signed-off-by: Jack Palevich --- src/jackpal/androidterm/session/TermSession.java | 2 +- src/jackpal/androidterm/util/TermSettings.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/jackpal/androidterm/session/TermSession.java b/src/jackpal/androidterm/session/TermSession.java index 213d52208..e853d907d 100644 --- a/src/jackpal/androidterm/session/TermSession.java +++ b/src/jackpal/androidterm/session/TermSession.java @@ -72,7 +72,7 @@ public class TermSession { private static final String DEFAULT_SHELL = "/system/bin/sh -"; private static final String DEFAULT_INITIAL_COMMAND = "export PATH=/data/local/bin:$PATH"; - private static final String DEFAULT_TERMTYPE = "vt100"; + private static final String DEFAULT_TERMTYPE = "screen"; // Number of rows in the transcript private static final int TRANSCRIPT_ROWS = 10000; diff --git a/src/jackpal/androidterm/util/TermSettings.java b/src/jackpal/androidterm/util/TermSettings.java index ea2fd868d..8caf355d0 100644 --- a/src/jackpal/androidterm/util/TermSettings.java +++ b/src/jackpal/androidterm/util/TermSettings.java @@ -38,7 +38,7 @@ public class TermSettings { private boolean mUTF8ByDefault = false; private int mBackKeyAction = BACK_KEY_SENDS_ESC; private String mTermType; - private boolean mCloseOnExit = false; + private boolean mCloseOnExit = true; private static final String STATUSBAR_KEY = "statusbar"; private static final String CURSORSTYLE_KEY = "cursorstyle"; @@ -119,7 +119,7 @@ public void readPrefs(SharedPreferences prefs) { mUTF8ByDefault = readBooleanPref(UTF8_KEY, false); mBackKeyAction = readIntPref(BACKACTION_KEY, mBackKeyAction, BACK_KEY_MAX); mTermType = readStringPref(TERMTYPE_KEY, mTermType); - mCloseOnExit = readBooleanPref(CLOSEONEXIT_KEY, false); + mCloseOnExit = readBooleanPref(CLOSEONEXIT_KEY, mCloseOnExit); mPrefs = null; // we leak a Context if we hold on to this } From 31326da6eb5751d941436c773aa1792fe68bdc72 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Mon, 21 Nov 2011 16:56:37 -0800 Subject: [PATCH 165/847] Refuse to draw on a finished TranscriptScreen It's not clear why we try to draw on a finished TranscriptScreen when exiting on Honeycomb (and not on other platforms), but this works around the resulting crash. Signed-off-by: Jack Palevich --- src/jackpal/androidterm/session/TranscriptScreen.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/jackpal/androidterm/session/TranscriptScreen.java b/src/jackpal/androidterm/session/TranscriptScreen.java index d10967663..64bd2cfd9 100644 --- a/src/jackpal/androidterm/session/TranscriptScreen.java +++ b/src/jackpal/androidterm/session/TranscriptScreen.java @@ -178,6 +178,10 @@ public final void drawText(int row, Canvas canvas, float x, float y, } catch (IllegalArgumentException e) { // Out-of-bounds rows are blank. return; + } catch (NullPointerException e) { + // Attempt to draw on a finished transcript + // XXX Figure out why this happens on Honeycomb + return; } int defaultForeColor = mData.getDefaultForeColor(); int defaultBackColor = mData.getDefaultBackColor(); From 5384887f3fbebde1ad41912d85f60d502fdeeb98 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Mon, 21 Nov 2011 20:06:46 -0800 Subject: [PATCH 166/847] Avoid polling for window size change on Android API level > 5 --- src/jackpal/androidterm/EmulatorView.java | 38 ++++++++++++++++++----- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/src/jackpal/androidterm/EmulatorView.java b/src/jackpal/androidterm/EmulatorView.java index 9b632ca2f..c7600513f 100644 --- a/src/jackpal/androidterm/EmulatorView.java +++ b/src/jackpal/androidterm/EmulatorView.java @@ -51,6 +51,7 @@ import jackpal.androidterm.session.TerminalEmulator; import jackpal.androidterm.session.TermSession; import jackpal.androidterm.session.TranscriptScreen; +import jackpal.androidterm.util.AndroidCompat; import jackpal.androidterm.util.TermSettings; /** @@ -175,15 +176,20 @@ public class EmulatorView extends View implements GestureDetector.OnGestureListe private int mSelY2 = -1; /** - * Used to poll if the view has changed size. Wish there was a better way to do this. + * True if we must poll to discover if the view has changed size. + * This is the only known way to detect the view changing size due to + * the IME being shown or hidden in Android 1.5 (API level 3) or 1.6 (API level 4). */ - private Runnable mCheckSize = new Runnable() { + private boolean mbPollForWindowSizeChange = AndroidCompat.SDK <= 4; - public void run() { - updateSize(false); - mHandler.postDelayed(this, SCREEN_CHECK_PERIOD); + private Runnable mCheckSize = mbPollForWindowSizeChange + ? new Runnable() { + public void run() { + updateSize(false); + mHandler.postDelayed(this, SCREEN_CHECK_PERIOD); + } } - }; + : null; private Runnable mBlinkCursor = new Runnable() { public void run() { @@ -239,14 +245,18 @@ public void setDensity(DisplayMetrics metrics) { public void onResume() { updateSize(false); - mHandler.postDelayed(mCheckSize, SCREEN_CHECK_PERIOD); + if (mbPollForWindowSizeChange) { + mHandler.postDelayed(mCheckSize, SCREEN_CHECK_PERIOD); + } if (mCursorBlink != 0) { mHandler.postDelayed(mBlinkCursor, CURSOR_BLINK_PERIOD); } } public void onPause() { - mHandler.removeCallbacks(mCheckSize); + if (mbPollForWindowSizeChange) { + mHandler.removeCallbacks(mCheckSize); + } if (mCursorBlink != 0) { mHandler.removeCallbacks(mBlinkCursor); } @@ -938,6 +948,18 @@ public void updateSize(boolean force) { } } + /** + * Called when the view changes size. + * (Note: Not always called on Android 1.5 or Android 1.6) + */ + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + if (this == mViewFlipper.getCurrentView()) { + updateSize(false); + } + } + @Override protected void onDraw(Canvas canvas) { updateSize(false); From 8a57df0c98589bd6e4f8e7dc3a1977a2e77a5b7b Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Mon, 21 Nov 2011 20:18:25 -0800 Subject: [PATCH 167/847] Make the soft keyboard always visible when ATE starts. Not really "always". It won't be made visibile if there is a hardware keyboard on the device or if there is a bluetooth keyboard currently connected to the device. --- AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index c4b7d2fc7..2056e6fa2 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -11,7 +11,7 @@ android:theme="@style/Theme" android:launchMode="singleTask" android:configChanges="keyboard|keyboardHidden|orientation" - android:windowSoftInputMode="adjustResize|stateVisible"> + android:windowSoftInputMode="adjustResize|stateAlwaysVisible"> From 0bc64f8aa7252fec774e1ca1417e662383cd4738 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Mon, 21 Nov 2011 22:12:49 -0800 Subject: [PATCH 168/847] Remove drop shadow at top of text window. It makes no sense in full screen mode, and it obscures the top few pixels of terminal text in both full screen and non-full-screen mode. --- res/values/styles.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/res/values/styles.xml b/res/values/styles.xml index 213c38d99..fe5892c40 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -18,6 +18,7 @@ From b724257fe33fb9637193149b7152f592ff3fcb8f Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Mon, 21 Nov 2011 22:18:03 -0800 Subject: [PATCH 169/847] Fix calculation of visible window size. There's a bug in pre-Android-SDK 10 when the view is full screen. See the discussion here: http://stackoverflow.com/questions/7659652 --- src/jackpal/androidterm/EmulatorView.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/jackpal/androidterm/EmulatorView.java b/src/jackpal/androidterm/EmulatorView.java index c7600513f..837f5b724 100644 --- a/src/jackpal/androidterm/EmulatorView.java +++ b/src/jackpal/androidterm/EmulatorView.java @@ -913,7 +913,6 @@ private void updateText() { @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { - boolean oldKnownSize = mKnownSize; if (!mKnownSize) { mKnownSize = true; } @@ -937,6 +936,12 @@ private void updateSize(int w, int h) { public void updateSize(boolean force) { if (mKnownSize) { getWindowVisibleDisplayFrame(mVisibleRect); + // Work around bug in getWindowVisibleDisplayFrame + if (AndroidCompat.SDK < 10) { + if (!mSettings.showStatusBar()) { + mVisibleRect.top = 0; + } + } int w = mVisibleRect.width(); int h = mVisibleRect.height(); // Log.w("Term", "(" + w + ", " + h + ")"); From a1088ab68d149f27e7931ae44387c87da079adac Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Mon, 21 Nov 2011 23:13:09 -0800 Subject: [PATCH 170/847] Fix text renderer to draw text in correct position on screen Was drawing slightly too low, which resulted in empty space on top, cut-off characters at the bottom. --- src/jackpal/androidterm/EmulatorView.java | 25 +++++++++++++++---- .../androidterm/model/TextRenderer.java | 2 ++ 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/jackpal/androidterm/EmulatorView.java b/src/jackpal/androidterm/EmulatorView.java index 837f5b724..3862cbddb 100644 --- a/src/jackpal/androidterm/EmulatorView.java +++ b/src/jackpal/androidterm/EmulatorView.java @@ -93,6 +93,11 @@ public class EmulatorView extends View implements GestureDetector.OnGestureListe */ private int mCharacterHeight; + /** + * Top-of-screen margin + */ + private int mTopOfScreenMargin; + /** * Used to render text */ @@ -921,9 +926,10 @@ protected void onSizeChanged(int w, int h, int oldw, int oldh) { private void updateSize(int w, int h) { mColumns = Math.max(1, (int) (((float) w) / mCharacterWidth)); - mRows = Math.max(1, h / mCharacterHeight); mVisibleColumns = (int) (((float) mVisibleWidth) / mCharacterWidth); + mTopOfScreenMargin = mTextRenderer.getTopMargin(); + mRows = Math.max(1, (h - mTopOfScreenMargin) / mCharacterHeight); mTermSession.updateSize(mColumns, mRows); // Reset our paging: @@ -970,9 +976,10 @@ protected void onDraw(Canvas canvas) { updateSize(false); int w = getWidth(); int h = getHeight(); + canvas.drawRect(0, 0, w, h, mBackgroundPaint); float x = -mLeftColumn * mCharacterWidth; - float y = mCharacterHeight; + float y = mCharacterHeight + mTopOfScreenMargin; int endLine = mTopRow + mRows; int cx = mEmulator.getCursorCol(); int cy = mEmulator.getCursorRow(); @@ -1097,6 +1104,10 @@ public int getCharacterHeight() { return kCharacterHeight; } + public int getTopMargin() { + return 0; + } + public void drawTextRun(Canvas canvas, float x, float y, int lineOffset, int runWidth, char[] text, int index, int count, boolean cursor, int foreColor, int backColor) { @@ -1174,8 +1185,8 @@ public void drawTextRun(Canvas canvas, float x, float y, int lineOffset, mTextPaint.setColor(mBackPaint[backColor & 0x7]); } float left = x + lineOffset * mCharWidth; - canvas.drawRect(left, y + mCharAscent, - left + runWidth * mCharWidth, y + mCharDescent, + canvas.drawRect(left, y + mCharAscent - mCharDescent, + left + runWidth * mCharWidth, y, mTextPaint); boolean bold = ( foreColor & 0x8 ) != 0; boolean underline = (backColor & 0x8) != 0; @@ -1186,7 +1197,7 @@ public void drawTextRun(Canvas canvas, float x, float y, int lineOffset, mTextPaint.setUnderlineText(true); } mTextPaint.setColor(mForePaint[foreColor]); - canvas.drawText(text, index, count, left, y, mTextPaint); + canvas.drawText(text, index, count, left, y - mCharDescent, mTextPaint); if (bold) { mTextPaint.setFakeBoldText(false); } @@ -1203,6 +1214,10 @@ public float getCharacterWidth() { return mCharWidth; } + public int getTopMargin() { + return mCharDescent; + } + private Paint mTextPaint; private float mCharWidth; diff --git a/src/jackpal/androidterm/model/TextRenderer.java b/src/jackpal/androidterm/model/TextRenderer.java index a064b266f..c8d64130a 100644 --- a/src/jackpal/androidterm/model/TextRenderer.java +++ b/src/jackpal/androidterm/model/TextRenderer.java @@ -25,6 +25,8 @@ public interface TextRenderer { float getCharacterWidth(); int getCharacterHeight(); + /** @return pixels above top row of text to avoid looking cramped. */ + int getTopMargin(); void drawTextRun(Canvas canvas, float x, float y, int lineOffset, int runWidth, char[] text, int index, int count, boolean cursor, int foreColor, int backColor); From e59ba73ea6c8ddf413c379174e4c047a0642b541 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Mon, 21 Nov 2011 23:14:47 -0800 Subject: [PATCH 171/847] Poll for window size changes when SDK <= 7 (Tested API levels 3 to 10 in emulator.) --- src/jackpal/androidterm/EmulatorView.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/jackpal/androidterm/EmulatorView.java b/src/jackpal/androidterm/EmulatorView.java index 3862cbddb..575d4de29 100644 --- a/src/jackpal/androidterm/EmulatorView.java +++ b/src/jackpal/androidterm/EmulatorView.java @@ -183,9 +183,9 @@ public class EmulatorView extends View implements GestureDetector.OnGestureListe /** * True if we must poll to discover if the view has changed size. * This is the only known way to detect the view changing size due to - * the IME being shown or hidden in Android 1.5 (API level 3) or 1.6 (API level 4). + * the IME being shown or hidden in API level <= 7. */ - private boolean mbPollForWindowSizeChange = AndroidCompat.SDK <= 4; + private boolean mbPollForWindowSizeChange = AndroidCompat.SDK <= 7; private Runnable mCheckSize = mbPollForWindowSizeChange ? new Runnable() { From 2597ae8b1caaca43095628717960a36a618aa2a4 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Mon, 21 Nov 2011 23:24:43 -0800 Subject: [PATCH 172/847] Update version number to 1.0.36 And version code to 37 --- AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 2056e6fa2..f8763f4cc 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,6 +1,6 @@ From 17b8ebe5d3cf8eef24f5303a69c25fcb4605e85d Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Tue, 22 Nov 2011 02:41:40 -0800 Subject: [PATCH 173/847] Revert default behavior of "Back Key" to exiting application. Users were confused by having the default be "ESC". Better to keep the default "exit the app", and let people manually change it. --- src/jackpal/androidterm/util/TermSettings.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jackpal/androidterm/util/TermSettings.java b/src/jackpal/androidterm/util/TermSettings.java index 8caf355d0..6b831032c 100644 --- a/src/jackpal/androidterm/util/TermSettings.java +++ b/src/jackpal/androidterm/util/TermSettings.java @@ -36,7 +36,7 @@ public class TermSettings { private String mShell; private String mInitialCommand; private boolean mUTF8ByDefault = false; - private int mBackKeyAction = BACK_KEY_SENDS_ESC; + private int mBackKeyAction = BACK_KEY_STOPS_SERVICE; private String mTermType; private boolean mCloseOnExit = true; From 9bc0c58a839a6121a7ca8fadbd3cc8f7e3d1f31b Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Tue, 22 Nov 2011 02:42:29 -0800 Subject: [PATCH 174/847] Version 1.0.37 --- AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index f8763f4cc..4e05515bf 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,6 +1,6 @@ From e054773bf923d5aa4337f507283c9570eb705c9d Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Tue, 22 Nov 2011 02:49:21 -0800 Subject: [PATCH 175/847] On further thought, switch to BACK_KEY_CLOSES_ACTIVITY by default. --- src/jackpal/androidterm/util/TermSettings.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jackpal/androidterm/util/TermSettings.java b/src/jackpal/androidterm/util/TermSettings.java index 6b831032c..0ee7bc999 100644 --- a/src/jackpal/androidterm/util/TermSettings.java +++ b/src/jackpal/androidterm/util/TermSettings.java @@ -36,7 +36,7 @@ public class TermSettings { private String mShell; private String mInitialCommand; private boolean mUTF8ByDefault = false; - private int mBackKeyAction = BACK_KEY_STOPS_SERVICE; + private int mBackKeyAction = BACK_KEY_CLOSES_ACTIVITY; private String mTermType; private boolean mCloseOnExit = true; From ed4fcbf724f253b429b0508e05216e882dcae176 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Tue, 22 Nov 2011 02:50:22 -0800 Subject: [PATCH 176/847] Version 1.0.38 --- AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 4e05515bf..6c17c9d07 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,6 +1,6 @@ From 5b5e0c2616ac44045a857ac1e5032b2d05977174 Mon Sep 17 00:00:00 2001 From: eauland Date: Tue, 22 Nov 2011 16:06:33 +0100 Subject: [PATCH 177/847] Update res/values-fr/arrays.xml --- res/values-fr/arrays.xml | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/res/values-fr/arrays.xml b/res/values-fr/arrays.xml index 1dad65bb7..2043fcb76 100644 --- a/res/values-fr/arrays.xml +++ b/res/values-fr/arrays.xml @@ -20,18 +20,18 @@ Afficher la barre de statut Masquer la barre de statut - + Curseur non clignotant Curseur clignotant - + Rectangle Souligné Barre verticale - + Texte noir sur blanc Texte blanc sur noir @@ -40,7 +40,15 @@ Texte orange sur noir Texte rouge sur noir - + + + Fermer toutes les fenêtres + Fermer la fenêtre en cours + Quitter en laissant les sessions activées + Simuler la touche ESC + Simuler la touche TAB + + Trackball Touche \@ @@ -50,7 +58,7 @@ Touche Vol Bas Touche Camera - + Trackball Touche \@ @@ -60,7 +68,7 @@ Touche Vol Bas Touche Camera - + Par caractère Par mot From e80e71754def0674aaadf59854482ccda20297c9 Mon Sep 17 00:00:00 2001 From: eauland Date: Tue, 22 Nov 2011 16:08:57 +0100 Subject: [PATCH 178/847] Update res/menu/main.xml --- res/menu/main.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/res/menu/main.xml b/res/menu/main.xml index 5c58dd766..064f833d1 100644 --- a/res/menu/main.xml +++ b/res/menu/main.xml @@ -36,8 +36,8 @@ android:title="@string/reset" /> - + - +

From 21fd016df61c74c5e8f13119850a4786991d72f0 Mon Sep 17 00:00:00 2001 From: eauland Date: Tue, 22 Nov 2011 19:18:54 +0100 Subject: [PATCH 179/847] Update res/values-fr/strings.xml --- res/values-fr/strings.xml | 50 ++++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml index c3d2ebea2..140457b7a 100644 --- a/res/values-fr/strings.xml +++ b/res/values-fr/strings.xml @@ -18,7 +18,7 @@ Terminal Emulator Préférences - Nlle fenêtre + Nouvelle fenêtre Fermer fenêtre Fenêtre Fenêtre Prec. @@ -27,72 +27,84 @@ Envoyer par e-mail Touches spéciales Afficher/Masquer Clavier - - + Activer WakeLock Désactiver WakeLock Activer WifiLock Désactiver WifiLock - + Modifier le texte Selectionner le texte Tout copier Coller Fenêtre %1$d - + Session terminal en cours d\'exécution Session terminal terminée - + Écran - + Barre de statut Afficher/Masquer la barre de statut Barre de statut - + Style du curseur Choisir le style du curseur Style du curseur - + Clignotement curseur Choisir si le curseur doit clignoter Clignotement curseur - + Texte UTF-8 par défaut Activer le mode UTF-8 par défaut - + Taille police Choisir la taille de la police de caractères en points Taille de la police - + Couleurs Choisir la couleur du texte Couleur du texte - + Clavier - + + Comportement bouton retour + Choisir l\'action du bouton retour quand il est cliqué + Comportement bouton retour + Touche CTRL - Choisir quelle touche utiliser pour control (CTRL) + Choisir quelle touche utiliser pour Control (CTRL) Touche CTRL - + Touche Fn Choisir quelle touche utiliser pour Fonction (Fn) Touche Fn - + Méthode d\'entrée Choisir la méthode d\'entrée du clavier virtuel Méthode d\'entrée - + Shell Ligne de commande Régler la ligne de commande du shell Shell - + Commande initiale Choisir une commande au démarrage du shell Commande initiale + + Type de terminal + Type de terminal à reporter au shell + Type de Terminal + + Quitter avec \'exit\' + Quitter le terminal en exécutant la commande \'exit\' + + Touches Control et Fonction From 516903aa296d18ee4a5f35f0813bdf68d70aab4b Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Tue, 22 Nov 2011 14:14:56 -0800 Subject: [PATCH 180/847] Make terminal window titles in window list activity localizable Thanks to Github user eauland for reporting. Signed-off-by: Jack Palevich --- src/jackpal/androidterm/WindowList.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jackpal/androidterm/WindowList.java b/src/jackpal/androidterm/WindowList.java index 523afad98..83ef074dd 100644 --- a/src/jackpal/androidterm/WindowList.java +++ b/src/jackpal/androidterm/WindowList.java @@ -60,7 +60,7 @@ public long getItemId(int position) { public View getView(int position, View convertView, ViewGroup parent) { View child = inflater.inflate(R.layout.window_list_item, parent, false); TextView label = (TextView) child.findViewById(R.id.window_list_label); - label.setText("Window " + (position+1)); + label.setText(getString(R.string.window_title, position + 1)); View close = child.findViewById(R.id.window_list_close); final TermService service = mTermService; From ae3341f497d16c12c07a2d24f7ac62fb418ad9d4 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Tue, 22 Nov 2011 14:15:25 -0800 Subject: [PATCH 181/847] Notify the EmulatorView of an update after resetting the terminal Signed-off-by: Jack Palevich --- src/jackpal/androidterm/session/TermSession.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/jackpal/androidterm/session/TermSession.java b/src/jackpal/androidterm/session/TermSession.java index e853d907d..0c8e39690 100644 --- a/src/jackpal/androidterm/session/TermSession.java +++ b/src/jackpal/androidterm/session/TermSession.java @@ -318,6 +318,9 @@ public void updatePrefs(TermSettings settings) { public void reset() { mEmulator.reset(); + if (mNotify != null) { + mNotify.onUpdate(); + } } /* XXX We should really get this ourselves from the resource bundle, but From a3915dd42226031e5fad69746ba09ca0ff8f007c Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Tue, 22 Nov 2011 20:09:59 -0800 Subject: [PATCH 182/847] Version 1.0.39 --- AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 6c17c9d07..901cab048 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,6 +1,6 @@ From 290a023167ceddbc9966a75d5f383f58f5f86c9a Mon Sep 17 00:00:00 2001 From: eauland Date: Wed, 23 Nov 2011 11:00:56 +0100 Subject: [PATCH 183/847] Update res/values-fr/strings.xml --- res/values-fr/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml index 140457b7a..cfce94063 100644 --- a/res/values-fr/strings.xml +++ b/res/values-fr/strings.xml @@ -74,7 +74,7 @@ Clavier - Comportement bouton retour + Bouton retour Choisir l\'action du bouton retour quand il est cliqué Comportement bouton retour @@ -103,8 +103,8 @@ Type de terminal à reporter au shell Type de Terminal - Quitter avec \'exit\' - Quitter le terminal en exécutant la commande \'exit\' + Quitter avec exit + Quitter le terminal en exécutant la commande exit Touches Control et Fonction From 203c4f591452e7191ff92174a8e5a692477d62f9 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Tue, 22 Nov 2011 20:17:58 -0800 Subject: [PATCH 184/847] Use Android 2.0 or later back key event tracking where possible Our key event tracking probably won't ever work as well as the system built into Android, so use Android's whenever possible (e.g. on API >= 5 devices). Signed-off-by: Jack Palevich --- src/jackpal/androidterm/Term.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index f23f22240..7d4e69bf9 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -52,6 +52,7 @@ import jackpal.androidterm.model.UpdateCallback; import jackpal.androidterm.session.TermSession; +import jackpal.androidterm.util.AndroidCompat; import jackpal.androidterm.util.SessionList; import jackpal.androidterm.util.TermSettings; @@ -454,7 +455,7 @@ public boolean onKeyDown(int keyCode, KeyEvent event) { /* The pre-Eclair default implementation of onKeyDown() would prevent our handling of the Back key in onKeyUp() from taking effect, so ignore it here */ - if (keyCode == KeyEvent.KEYCODE_BACK) { + if (AndroidCompat.SDK < 5 && keyCode == KeyEvent.KEYCODE_BACK) { /* Android pre-Eclair has no key event tracking, and a back key down event delivered to an activity above us in the back stack could be succeeded by a back key up event to us, so we need to @@ -470,12 +471,14 @@ our handling of the Back key in onKeyUp() from taking effect, so public boolean onKeyUp(int keyCode, KeyEvent event) { switch (keyCode) { case KeyEvent.KEYCODE_BACK: - if (!mBackKeyPressed) { - /* This key up event might correspond to a key down delivered - to another activity -- ignore */ - return false; + if (AndroidCompat.SDK < 5) { + if (!mBackKeyPressed) { + /* This key up event might correspond to a key down + delivered to another activity -- ignore */ + return false; + } + mBackKeyPressed = false; } - mBackKeyPressed = false; switch (mSettings.getBackKeyAction()) { case TermSettings.BACK_KEY_STOPS_SERVICE: mStopServiceOnFinish = true; From b8df6d759f8f8962083c88e5a77f0be419c5f93d Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Tue, 22 Nov 2011 20:18:04 -0800 Subject: [PATCH 185/847] Reset back key pressed state in onPause() This prevents us from incorrectly consuming a back key up event in the following scenario on Android < 2.0: (1) User presses back key down in the terminal activity (2) While the back key is down, another activity moves to the top of our task's back stack (3) User releases the back key (4) User presses the back key down again, returning focus to us (5) User releases the back key Signed-off-by: Jack Palevich --- src/jackpal/androidterm/Term.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index 7d4e69bf9..357549a5f 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -287,6 +287,13 @@ public void onPause() { mTermSessions.removeCallback(this); } + if (AndroidCompat.SDK < 5) { + /* If we lose focus between a back key down and a back key up, + we shouldn't respond to the next back key up event unless + we get another key down first */ + mBackKeyPressed = false; + } + /* Explicitly close the input method Otherwise, the soft keyboard could cover up whatever activity takes our place */ From 9e5a4b871b393e09c6736882265e3468f8212b39 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Tue, 22 Nov 2011 20:18:10 -0800 Subject: [PATCH 186/847] Keep all default preferences in one place We keep forgetting to change bits and pieces which contain default preferences when we update them. That's nuts, so let's keep all the default preferences in one file (res/value/defaults.xml) in the resources, and fetch those preferences from the resources whenever we need them. Signed-off-by: Jack Palevich --- res/values/defaults.xml | 18 +++++++ res/values/strings.xml | 14 ------ res/xml/preferences.xml | 28 +++++------ src/jackpal/androidterm/Term.java | 2 +- .../androidterm/session/TermSession.java | 13 ----- .../androidterm/util/TermSettings.java | 47 ++++++++++++++----- 6 files changed, 67 insertions(+), 55 deletions(-) create mode 100644 res/values/defaults.xml diff --git a/res/values/defaults.xml b/res/values/defaults.xml new file mode 100644 index 000000000..72619ba68 --- /dev/null +++ b/res/values/defaults.xml @@ -0,0 +1,18 @@ + + + + 0 + 0 + 0 + 10 + 2 + false + 2 + 5 + 4 + 0 + /system/bin/sh - + export PATH=/data/local/bin:$PATH + screen + true + diff --git a/res/values/strings.xml b/res/values/strings.xml index d8a22162f..a8b023e5e 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -105,20 +105,6 @@ Close window on exit Whether a window should close when its shell exits. - - 0 - 0 - 0 - 10 - 2 - 3 - 5 - 4 - 0 - /system/bin/sh - - export PATH=/data/local/bin:$PATH - screen - Control and Function Keys diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml index 787217c0d..55991c34e 100644 --- a/res/xml/preferences.xml +++ b/res/xml/preferences.xml @@ -22,7 +22,7 @@ @@ -86,7 +86,7 @@ diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index 357549a5f..406851b97 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -113,7 +113,7 @@ public void onCreate(Bundle icicle) { super.onCreate(icicle); Log.e(TermDebug.LOG_TAG, "onCreate"); mPrefs = PreferenceManager.getDefaultSharedPreferences(this); - mSettings = new TermSettings(mPrefs); + mSettings = new TermSettings(getResources(), mPrefs); TSIntent = new Intent(this, TermService.class); startService(TSIntent); diff --git a/src/jackpal/androidterm/session/TermSession.java b/src/jackpal/androidterm/session/TermSession.java index 0c8e39690..821bcd7ac 100644 --- a/src/jackpal/androidterm/session/TermSession.java +++ b/src/jackpal/androidterm/session/TermSession.java @@ -69,10 +69,6 @@ public class TermSession { private static final int DEFAULT_COLUMNS = 80; private static final int DEFAULT_ROWS = 24; - private static final String DEFAULT_SHELL = "/system/bin/sh -"; - private static final String DEFAULT_INITIAL_COMMAND = - "export PATH=/data/local/bin:$PATH"; - private static final String DEFAULT_TERMTYPE = "screen"; // Number of rows in the transcript private static final int TRANSCRIPT_ROWS = 10000; @@ -161,9 +157,6 @@ public void run() { } private void sendInitialCommand(String initialCommand) { - if (initialCommand == null || initialCommand.equals("")) { - initialCommand = DEFAULT_INITIAL_COMMAND; - } if (initialCommand.length() > 0) { write(initialCommand + '\r'); } @@ -200,17 +193,11 @@ public void write(int codePoint) { private void createSubprocess(int[] processId) { String shell = mSettings.getShell(); - if (shell == null || shell.equals("")) { - shell = DEFAULT_SHELL; - } ArrayList argList = parse(shell); String arg0 = argList.get(0); String[] args = argList.toArray(new String[1]); String termType = mSettings.getTermType(); - if (termType == null) { - termType = DEFAULT_TERMTYPE; - } String[] env = new String[1]; env[0] = "TERM=" + termType; diff --git a/src/jackpal/androidterm/util/TermSettings.java b/src/jackpal/androidterm/util/TermSettings.java index 0ee7bc999..e0230ccdf 100644 --- a/src/jackpal/androidterm/util/TermSettings.java +++ b/src/jackpal/androidterm/util/TermSettings.java @@ -16,29 +16,32 @@ package jackpal.androidterm.util; +import android.content.res.Resources; import android.content.SharedPreferences; import android.view.KeyEvent; +import jackpal.androidterm.R; + /** * Terminal emulator settings */ public class TermSettings { private SharedPreferences mPrefs; - private int mStatusBar = 0; - private int mCursorStyle = 0; - private int mCursorBlink = 0; - private int mFontSize = 9; - private int mColorId = 2; - private int mControlKeyId = 5; // Default to Volume Down - private int mFnKeyId = 4; // Default to Volume Up - private int mUseCookedIME = 0; + private int mStatusBar; + private int mCursorStyle; + private int mCursorBlink; + private int mFontSize; + private int mColorId; + private int mControlKeyId; + private int mFnKeyId; + private int mUseCookedIME; private String mShell; private String mInitialCommand; - private boolean mUTF8ByDefault = false; - private int mBackKeyAction = BACK_KEY_CLOSES_ACTIVITY; + private boolean mUTF8ByDefault; + private int mBackKeyAction; private String mTermType; - private boolean mCloseOnExit = true; + private boolean mCloseOnExit; private static final String STATUSBAR_KEY = "statusbar"; private static final String CURSORSTYLE_KEY = "cursorstyle"; @@ -98,10 +101,28 @@ public class TermSettings { public static final int BACK_KEY_SENDS_TAB = 4; private static final int BACK_KEY_MAX = 4; - public TermSettings(SharedPreferences prefs) { + public TermSettings(Resources res, SharedPreferences prefs) { + readDefaultPrefs(res); readPrefs(prefs); } + private void readDefaultPrefs(Resources res) { + mStatusBar = Integer.parseInt(res.getString(R.string.pref_statusbar_default)); + mCursorStyle = Integer.parseInt(res.getString(R.string.pref_cursorstyle_default)); + mCursorBlink = Integer.parseInt(res.getString(R.string.pref_cursorblink_default)); + mFontSize = Integer.parseInt(res.getString(R.string.pref_fontsize_default)); + mColorId = Integer.parseInt(res.getString(R.string.pref_color_default)); + mUTF8ByDefault = res.getBoolean(R.bool.pref_utf8_by_default_default); + mBackKeyAction = Integer.parseInt(res.getString(R.string.pref_backaction_default)); + mControlKeyId = Integer.parseInt(res.getString(R.string.pref_controlkey_default)); + mFnKeyId = Integer.parseInt(res.getString(R.string.pref_fnkey_default)); + mUseCookedIME = Integer.parseInt(res.getString(R.string.pref_ime_default)); + mShell = res.getString(R.string.pref_shell_default); + mInitialCommand = res.getString(R.string.pref_initialcommand_default); + mTermType = res.getString(R.string.pref_termtype_default); + mCloseOnExit = res.getBoolean(R.bool.pref_close_window_on_process_exit_default); + } + public void readPrefs(SharedPreferences prefs) { mPrefs = prefs; mStatusBar = readIntPref(STATUSBAR_KEY, mStatusBar, 1); @@ -116,7 +137,7 @@ public void readPrefs(SharedPreferences prefs) { mUseCookedIME = readIntPref(IME_KEY, mUseCookedIME, 1); mShell = readStringPref(SHELL_KEY, mShell); mInitialCommand = readStringPref(INITIALCOMMAND_KEY, mInitialCommand); - mUTF8ByDefault = readBooleanPref(UTF8_KEY, false); + mUTF8ByDefault = readBooleanPref(UTF8_KEY, mUTF8ByDefault); mBackKeyAction = readIntPref(BACKACTION_KEY, mBackKeyAction, BACK_KEY_MAX); mTermType = readStringPref(TERMTYPE_KEY, mTermType); mCloseOnExit = readBooleanPref(CLOSEONEXIT_KEY, mCloseOnExit); From 28837a06ffa92c6b6bd9c2cea98536c0ae1b7f9c Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Tue, 22 Nov 2011 20:18:22 -0800 Subject: [PATCH 187/847] Tidy up after the default preferences patch * Keep variables and functions in TermSettings.java in the order corresponding to their appearance in preferences.xml * Remove old and unnecessary default_value_*_preference strings from translations (silences warnings) Signed-off-by: Jack Palevich --- res/values-eu/strings.xml | 12 ----- res/values-tr/strings.xml | 11 ---- .../androidterm/util/TermSettings.java | 51 ++++++++++--------- 3 files changed, 26 insertions(+), 48 deletions(-) diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml index b88bfdb74..7144e146f 100644 --- a/res/values-eu/strings.xml +++ b/res/values-eu/strings.xml @@ -87,18 +87,6 @@ Bidali shell-era abiarazten denean. Hasierako Komandoa - - 0 - 0 - 0 - 10 - 2 - 5 - 4 - 0 - /system/bin/sh - - export PATH=/data/local/bin:$PATH - Kontrol and Funtzio Teklak diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml index 7462d5876..b45fa127e 100644 --- a/res/values-tr/strings.xml +++ b/res/values-tr/strings.xml @@ -70,15 +70,4 @@ Başlangıç komutu Başlangıçta kabukta çalıştır. Başlangıç komutu - - - 0 - 0 - 0 - 10 - 2 - 0 - 0 - /system/bin/sh - - export PATH=/data/local/bin:$PATH diff --git a/src/jackpal/androidterm/util/TermSettings.java b/src/jackpal/androidterm/util/TermSettings.java index e0230ccdf..1870172c0 100644 --- a/src/jackpal/androidterm/util/TermSettings.java +++ b/src/jackpal/androidterm/util/TermSettings.java @@ -33,13 +33,13 @@ public class TermSettings { private int mCursorBlink; private int mFontSize; private int mColorId; + private boolean mUTF8ByDefault; + private int mBackKeyAction; private int mControlKeyId; private int mFnKeyId; private int mUseCookedIME; private String mShell; private String mInitialCommand; - private boolean mUTF8ByDefault; - private int mBackKeyAction; private String mTermType; private boolean mCloseOnExit; @@ -48,13 +48,13 @@ public class TermSettings { private static final String CURSORBLINK_KEY = "cursorblink"; private static final String FONTSIZE_KEY = "fontsize"; private static final String COLOR_KEY = "color"; + private static final String UTF8_KEY = "utf8_by_default"; + private static final String BACKACTION_KEY = "backaction"; private static final String CONTROLKEY_KEY = "controlkey"; private static final String FNKEY_KEY = "fnkey"; private static final String IME_KEY = "ime"; private static final String SHELL_KEY = "shell"; private static final String INITIALCOMMAND_KEY = "initialcommand"; - private static final String UTF8_KEY = "utf8_by_default"; - private static final String BACKACTION_KEY = "backaction"; private static final String TERMTYPE_KEY = "termtype"; private static final String CLOSEONEXIT_KEY = "close_window_on_process_exit"; @@ -130,6 +130,8 @@ public void readPrefs(SharedPreferences prefs) { // mCursorBlink = readIntPref(CURSORBLINK_KEY, mCursorBlink, 1); mFontSize = readIntPref(FONTSIZE_KEY, mFontSize, 20); mColorId = readIntPref(COLOR_KEY, mColorId, COLOR_SCHEMES.length - 1); + mUTF8ByDefault = readBooleanPref(UTF8_KEY, mUTF8ByDefault); + mBackKeyAction = readIntPref(BACKACTION_KEY, mBackKeyAction, BACK_KEY_MAX); mControlKeyId = readIntPref(CONTROLKEY_KEY, mControlKeyId, CONTROL_KEY_SCHEMES.length - 1); mFnKeyId = readIntPref(FNKEY_KEY, mFnKeyId, @@ -137,8 +139,6 @@ public void readPrefs(SharedPreferences prefs) { mUseCookedIME = readIntPref(IME_KEY, mUseCookedIME, 1); mShell = readStringPref(SHELL_KEY, mShell); mInitialCommand = readStringPref(INITIALCOMMAND_KEY, mInitialCommand); - mUTF8ByDefault = readBooleanPref(UTF8_KEY, mUTF8ByDefault); - mBackKeyAction = readIntPref(BACKACTION_KEY, mBackKeyAction, BACK_KEY_MAX); mTermType = readStringPref(TERMTYPE_KEY, mTermType); mCloseOnExit = readBooleanPref(CLOSEONEXIT_KEY, mCloseOnExit); mPrefs = null; // we leak a Context if we hold on to this @@ -184,6 +184,26 @@ public int[] getColorScheme() { return COLOR_SCHEMES[mColorId]; } + public boolean defaultToUTF8Mode() { + return mUTF8ByDefault; + } + + public int getBackKeyAction() { + return mBackKeyAction; + } + + public boolean backKeySendsCharacter() { + return mBackKeyAction >= BACK_KEY_SENDS_ESC; + } + + public int getBackKeyCharacter() { + switch (mBackKeyAction) { + case BACK_KEY_SENDS_ESC: return 27; + case BACK_KEY_SENDS_TAB: return 9; + default: return 0; + } + } + public int getControlKeyId() { return mControlKeyId; } @@ -212,25 +232,6 @@ public String getInitialCommand() { return mInitialCommand; } - public boolean defaultToUTF8Mode() { - return mUTF8ByDefault; - } - - public int getBackKeyAction() { - return mBackKeyAction; - } - - public boolean backKeySendsCharacter() { - return mBackKeyAction >= BACK_KEY_SENDS_ESC; - } - public int getBackKeyCharacter() { - switch (mBackKeyAction) { - case BACK_KEY_SENDS_ESC: return 27; - case BACK_KEY_SENDS_TAB: return 9; - default: return 0; - } - } - public String getTermType() { return mTermType; } From aa98cffdb05161bcaa160161a506b716e19b0437 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Wed, 23 Nov 2011 10:33:18 -0800 Subject: [PATCH 188/847] Fix indentation on manifest file. --- AndroidManifest.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 901cab048..849aa66a2 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -2,9 +2,9 @@ package="jackpal.androidterm" android:versionName="1.0.39" android:versionCode="40" android:installLocation="auto"> - - - + + + Date: Fri, 25 Nov 2011 23:58:05 +0100 Subject: [PATCH 189/847] Updated german translation --- res/values-de/arrays.xml | 47 +++++++++++- res/values-de/strings.xml | 157 ++++++++++++++++++++++++-------------- 2 files changed, 147 insertions(+), 57 deletions(-) diff --git a/res/values-de/arrays.xml b/res/values-de/arrays.xml index 1bd3287fd..6b93f2212 100644 --- a/res/values-de/arrays.xml +++ b/res/values-de/arrays.xml @@ -54,6 +54,14 @@ Roter Text auf schwarz + + Schliesst alle Terminal Fenster + Schliesst nur dieses Terminal Fenster + Aktivität schliessen, Sessions erhalten lassen + Sendet ESC zum Terminal + Sendet TAB zum Terminal + + Trackball \@ Taste @@ -61,10 +69,47 @@ Rechte Alt-Taste Lauter Leiser + Kamera Taste + Keine - + + + Trackball + \@ Taste + Linke Alt-Taste + Rechte Alt-Taste + Lauter + Leiser + Kamera Taste + Keine + + Zeichen-basiert Wort-basiert + + + + Trackball + \@ Taste + Linke Alt-Taste + Rechte Alt-Taste + Lauter + Leiser + Kamera Taste + Keine + + + + + Trackball + \@ Taste + Linke Alt-Taste + Rechte Alt-Taste + Lauter + Leiser + Kamera Taste + Keine + diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml index 9451a3d01..363a11821 100644 --- a/res/values-de/strings.xml +++ b/res/values-de/strings.xml @@ -1,72 +1,117 @@ + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +--> - Terminal Emulator - Einstellungen - Zurücksetzen - Email an - Spezialtasten - Tastatur an/aus + Terminal Emulator + Einstellungen + Neues Fenster + Fenster schliessen + Fenster + Vorh. Fenster + Nächst. Fenster + Zurücksetzen + Email an + Spezialtasten + Tastatur an/aus - Text bearbeiten - Alles kopieren - Einfügen + Take WakeLock + Drop WakeLock + Take WifiLock + Drop WifiLock + + Text bearbeiten + Text auswählen + Alles kopieren + Einfügen + + Terminal Sitzung wird ausgeführt - - Bildschirm + Fenster %1$d - Statuszeile - Die Statuszeile anzeigen/verbergen. - Statuszeile + Terminal Sitzung beendet + + Bildschirm - Aussehen Cursor - Das Aussehen des Cursors auswählen. - Aussehen Cursor + Statuszeile + Die Statuszeile anzeigen/verbergen. + Statuszeile - Cursor-Blinken - Die Art auswählen, wie der Cursor blinken soll. - Cursor-Blinken + Aussehen Cursor + Das Aussehen des Cursors auswählen. + Aussehen Cursor - Text + Cursor-Blinken + Die Art auswählen, wie der Cursor blinken soll. + Cursor-Blinken - Schriftgröße - Die Zeichengröße in Punkten auswählen. - Schriftgröße + Text - Farben - Die Textfarben auswählen. - Textfarbe + UTF-8 als Standard definieren + Ab der UTF-8 Modus standartmässig eingeschaltet werden soll. - Tastatur + Schriftgröße + Die Zeichengröße in Punkten auswählen. + Schriftgröße - Steuerungstaste - Die Steuerungstaste auswählen. - Steuerungstaste + Farben + Die Textfarben auswählen. + Textfarbe - Eingabemethode - Die Eingabemethode für die Soft-Tastatur auswählen. - Eingabemethode + Tastatur - Shell - Shell - Die zu verwendene Shell auswählen. - Shell + Zurück-Knopf Verhalten + Festlegen der Aktion bei drücken des Zurück-Knopfes. + Zurück-Knopf Verhalten - Startkommando - Kommando eingeben, dass beim Start an die Shell gesendet wird. - Startkommando - + Steuerungstaste + Die Steuerungstaste auswählen. + Steuerungstaste + + Fn Taste + Wähle Fn Taste. + Fn Taste + + Eingabemethode + Die Eingabemethode für die Soft-Tastatur auswählen. + Eingabemethode + + Shell + Shell + Die zu verwendene Shell auswählen. + Shell + + Startkommando + Kommando eingeben, dass beim Start an die Shell gesendet wird. + Startkommando + + Terminal Typ + Was für einen Terminal Typ soll die Shell Ausführen. + Terminal Typ + + Bei Programmbeendigung Fenster schliessen + Ob das Fenster geschlossen werden soll wenn die Shell geschlossen wird. + + Kontrol- and Funktionstasten + + CTRLKEY Space : Control-@ (NUL)\nCTRLKEY A..Z : Control-A..Z\nCTRLKEY 5 : Control-]\nCTRLKEY 6 : Control-^\nCTRLKEY 7 : Control-_\nCTRLKEY 9 : F11\nCTRLKEY 0 : F12 + Keine Kontroltaste festgelegt. + + FNKEY 1..9 : F1-F9\nFNKEY 0 : F10\nFNKEY W : Up\nFNKEY A : Left\nFNKEY S : Down\nFNKEY D : Right\nFNKEY P : PageUp\nFNKEY N : PageDown\nFNKEY T : Tab\nFNKEY L : | (pipe)\nFNKEY U : _ (underscore)\nFNKEY E : Control-[ (ESC)\nFNKEY X : Delete\nFNKEY I : Insert\nFNKEY H : Home\nFNKEY F : End\nFNKEY . : Control-\\\n + Keine Funktionstaste fergelegt. + + \ No newline at end of file From 0c10386ba62645252ebf862ac1a8efc4bd399f33 Mon Sep 17 00:00:00 2001 From: eauland Date: Mon, 28 Nov 2011 12:24:33 +0100 Subject: [PATCH 190/847] Update res/values-fr/strings.xml --- res/values-fr/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml index cfce94063..e7685dfa8 100644 --- a/res/values-fr/strings.xml +++ b/res/values-fr/strings.xml @@ -76,7 +76,7 @@ Bouton retour Choisir l\'action du bouton retour quand il est cliqué - Comportement bouton retour + Bouton retour Touche CTRL Choisir quelle touche utiliser pour Control (CTRL) From e6e37e95256249cc0b602303368aaf880dc61c14 Mon Sep 17 00:00:00 2001 From: damor Date: Mon, 28 Nov 2011 19:26:36 +0100 Subject: [PATCH 191/847] Added portuguese (pt) localization --- res/values-pt/arrays.xml | 115 +++++++++++++++++++++++++++++++++++++ res/values-pt/strings.xml | 118 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 233 insertions(+) create mode 100644 res/values-pt/arrays.xml create mode 100644 res/values-pt/strings.xml diff --git a/res/values-pt/arrays.xml b/res/values-pt/arrays.xml new file mode 100644 index 000000000..5b85ff6b9 --- /dev/null +++ b/res/values-pt/arrays.xml @@ -0,0 +1,115 @@ + + + + + + Show status bar + Hide status bar + + + + Non-blinking cursor + Blinking cursor + + + + Rectangle + Underline + Vertical bar + + + + 4 x 8 pixels + 6 pt + 7 pt + 8 pt + 9 pt + 10 pt + 12 pt + 14 pt + 16 pt + 20 pt + + + + Black text on white + White text on black + White text on blue + Green text on black + Amber text on black + Red text on black + + + + Closes all terminal windows + Closes this terminal window only + Closes activity, leaving sessions running + Sends ESC to terminal + Sends TAB to terminal + + + + Jog ball + \@ key + Left Alt key + Right Alt key + Vol Up key + Vol Down key + Camera key + None + + + + Jog ball + \@ key + Left Alt key + Right Alt key + Vol Up key + Vol Down key + Camera key + None + + + + Character-based + Word-based + + + + + Ball + \@ + Left-Alt + Right-Alt + Vol-Up + Vol-Dn + Camera + None + + + + + Ball + \@ + Left-Alt + Right-Alt + Vol-Up + Vol-Dn + Camera + None + + \ No newline at end of file diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml new file mode 100644 index 000000000..a7616c5c5 --- /dev/null +++ b/res/values-pt/strings.xml @@ -0,0 +1,118 @@ + + + + Terminal Emulator + Preferences + New window + Close window + Windows + Prev window + Next window + Reset term + Email to + Special keys + Toggle soft keyboard + + Take WakeLock + Drop WakeLock + Take WifiLock + Drop WifiLock + + Edit text + Select text + Copy all + Paste + + Window %1$d + + Terminal session is running + + Terminal session finished + + + Screen + + Status bar + Show/hide status bar. + Status bar + + Cursor style + Choose cursor style. + Cursor style + + Cursor blink + Choose cursor blink. + Cursor blink + + Text + + Default to UTF-8 + Whether UTF-8 mode is enabled by default. + + Font size + Choose character height in points. + Font size + + Colors + Choose text color. + Text color + + Keyboard + + Back button behavior + Choose what pressing the back button does. + Back button behavior + + Control key + Choose control key. + Control key + + Fn key + Choose Fn key. + Fn key + + Input method + Choose input method for soft keyboard. + Input method + + Shell + Command line + Specify the shell command line. + Shell + + Initial command + Sent to the shell when it starts. + Initial Command + + Terminal type + What terminal type to report to the shell. + Terminal type + + Close window on exit + Whether a window should close when its shell exits. + + Control and Function Keys + + CTRLKEY Space : Control-@ (NUL)\nCTRLKEY A..Z : Control-A..Z\nCTRLKEY 5 : Control-]\nCTRLKEY 6 : Control-^\nCTRLKEY 7 : Control-_\nCTRLKEY 9 : F11\nCTRLKEY 0 : F12 + No control key set. + + FNKEY 1..9 : F1-F9\nFNKEY 0 : F10\nFNKEY W : Up\nFNKEY A : Left\nFNKEY S : Down\nFNKEY D : Right\nFNKEY P : PageUp\nFNKEY N : PageDown\nFNKEY T : Tab\nFNKEY L : | (pipe)\nFNKEY U : _ (underscore)\nFNKEY E : Control-[ (ESC)\nFNKEY X : Delete\nFNKEY I : Insert\nFNKEY H : Home\nFNKEY F : End\nFNKEY . : Control-\\\n + No function key set. + + \ No newline at end of file From 3ab1c4eac8c826fdff14cf855cb3f66aec1950cc Mon Sep 17 00:00:00 2001 From: damor Date: Tue, 29 Nov 2011 11:47:59 +0100 Subject: [PATCH 192/847] Display toast after terminal reset, fixes #55 --- res/values-de/strings.xml | 2 ++ res/values-fr/strings.xml | 2 ++ res/values-pt/strings.xml | 4 +++- res/values/strings.xml | 2 ++ src/jackpal/androidterm/Term.java | 1 + 5 files changed, 10 insertions(+), 1 deletion(-) diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml index 363a11821..41627a8d6 100644 --- a/res/values-de/strings.xml +++ b/res/values-de/strings.xml @@ -26,6 +26,8 @@ Email an Spezialtasten Tastatur an/aus + + Der Status dieses Terminalfensters wurde zurückgesetzt. Take WakeLock Drop WakeLock diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml index cfce94063..f3d22896a 100644 --- a/res/values-fr/strings.xml +++ b/res/values-fr/strings.xml @@ -28,6 +28,8 @@ Touches spéciales Afficher/Masquer Clavier + L\'état de cette fenêtre vient d\'être mis a défaut. + Activer WakeLock Désactiver WakeLock Activer WifiLock diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml index a7616c5c5..048965592 100644 --- a/res/values-pt/strings.xml +++ b/res/values-pt/strings.xml @@ -26,7 +26,9 @@ Email to Special keys Toggle soft keyboard - + + Estado d\'esta janela foi zerado. + Take WakeLock Drop WakeLock Take WifiLock diff --git a/res/values/strings.xml b/res/values/strings.xml index a8b023e5e..bfa7aed03 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -26,6 +26,8 @@ Email to Special keys Toggle soft keyboard + + This window\'s terminal state has been reset. Take WakeLock Drop WakeLock diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index 406851b97..923744710 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -336,6 +336,7 @@ public boolean onOptionsItemSelected(MenuItem item) { startActivityForResult(new Intent(this, WindowList.class), REQUEST_CHOOSE_WINDOW); } else if (id == R.id.menu_reset) { doResetTerminal(); + Toast.makeText(this,R.string.reset_toast_notification,Toast.LENGTH_SHORT).show(); } else if (id == R.id.menu_send_email) { doEmailTranscript(); } else if (id == R.id.menu_special_keys) { From e8af39bc519c83e95e7c266902843eca4372c98e Mon Sep 17 00:00:00 2001 From: Yang Tang Date: Thu, 1 Dec 2011 17:23:03 -0500 Subject: [PATCH 193/847] Add context menus for sending control/fn keys, for the sake of Kindle Fire. --- res/values/strings.xml | 2 ++ src/jackpal/androidterm/EmulatorView.java | 26 +++++++++++++++++++++++ src/jackpal/androidterm/Term.java | 18 ++++++++++++++++ 3 files changed, 46 insertions(+) diff --git a/res/values/strings.xml b/res/values/strings.xml index a8b023e5e..178464fd0 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -36,6 +36,8 @@ Select text Copy all Paste + Send control key + Send fn key Window %1$d diff --git a/src/jackpal/androidterm/EmulatorView.java b/src/jackpal/androidterm/EmulatorView.java index 575d4de29..6c5f58778 100644 --- a/src/jackpal/androidterm/EmulatorView.java +++ b/src/jackpal/androidterm/EmulatorView.java @@ -168,6 +168,9 @@ public class EmulatorView extends View implements GestureDetector.OnGestureListe private boolean mIsSelectingText = false; + private boolean mIsControlKeySent = false; + private boolean mIsFnKeySent = false; + private float mDensity; @@ -350,6 +353,7 @@ private void mapAndSend(int c) throws IOException { } else { mKeyListener.handleKeyCode(result - TermKeyListener.KEYCODE_OFFSET, getKeypadApplicationMode()); } + clearSpecialKeyStatus(); } public boolean beginBatchEdit() { @@ -868,6 +872,7 @@ public boolean onKeyUp(int keyCode, KeyEvent event) { } mKeyListener.keyUp(keyCode); + clearSpecialKeyStatus(); return true; } @@ -898,6 +903,17 @@ private boolean isSystemKey(int keyCode, KeyEvent event) { return event.isSystem(); } + private void clearSpecialKeyStatus() { + if (mIsControlKeySent) { + mIsControlKeySent = false; + mKeyListener.handleControlKey(false); + } + if (mIsFnKeySent) { + mIsFnKeySent = false; + mKeyListener.handleFnKey(false); + } + } + private void updateText() { if (mTextSize > 0) { mTextRenderer = new PaintRenderer(mTextSize, @@ -1036,6 +1052,16 @@ public boolean getSelectingText() { public String getSelectedText() { return mEmulator.getSelectedText(mSelX1, mSelY1, mSelX2, mSelY2); } + + public void sendControlKey() { + mIsControlKeySent = true; + mKeyListener.handleControlKey(true); + } + + public void sendFnKey() { + mIsFnKeySent = true; + mKeyListener.handleFnKey(true); + } } abstract class BaseTextRenderer implements TextRenderer { diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index 406851b97..bde87f6a6 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -79,6 +79,8 @@ public class Term extends Activity implements UpdateCallback { private final static int SELECT_TEXT_ID = 0; private final static int COPY_ALL_ID = 1; private final static int PASTE_ID = 2; + private final static int SEND_CONTROL_KEY_ID = 3; + private final static int SEND_FN_KEY_ID = 4; private boolean mAlreadyStarted = false; private boolean mStopServiceOnFinish = false; @@ -435,6 +437,8 @@ public void onCreateContextMenu(ContextMenu menu, View v, menu.add(0, SELECT_TEXT_ID, 0, R.string.select_text); menu.add(0, COPY_ALL_ID, 0, R.string.copy_all); menu.add(0, PASTE_ID, 0, R.string.paste); + menu.add(0, SEND_CONTROL_KEY_ID, 0, R.string.send_control_key); + menu.add(0, SEND_FN_KEY_ID, 0, R.string.send_fn_key); if (!canPaste()) { menu.getItem(PASTE_ID).setEnabled(false); } @@ -452,6 +456,12 @@ public boolean onContextItemSelected(MenuItem item) { case PASTE_ID: doPaste(); return true; + case SEND_CONTROL_KEY_ID: + doSendControlKey(); + return true; + case SEND_FN_KEY_ID: + doSendFnKey(); + return true; default: return super.onContextItemSelected(item); } @@ -577,6 +587,14 @@ private void doPaste() { getCurrentTermSession().write(paste.toString()); } + private void doSendControlKey() { + getCurrentEmulatorView().sendControlKey(); + } + + private void doSendFnKey() { + getCurrentEmulatorView().sendFnKey(); + } + private void doDocumentKeys() { AlertDialog.Builder dialog = new AlertDialog.Builder(this); Resources r = getResources(); From 1ebab0a6b134e4c576f5493e625e83eee903dbcf Mon Sep 17 00:00:00 2001 From: John Hsing Date: Thu, 8 Dec 2011 20:20:34 +0800 Subject: [PATCH 194/847] AndroidTerm: update Simplified Chinese translations Change-Id: I523b59205fd40bece394a8ccc7c543fdbf272f52 --- res/values-zh-rCN/arrays.xml | 81 +++++++++++++++++++++++++++++++++++ res/values-zh-rCN/strings.xml | 11 ++++- 2 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 res/values-zh-rCN/arrays.xml diff --git a/res/values-zh-rCN/arrays.xml b/res/values-zh-rCN/arrays.xml new file mode 100644 index 000000000..875151aec --- /dev/null +++ b/res/values-zh-rCN/arrays.xml @@ -0,0 +1,81 @@ + + + + + + 显示状态栏 + 隐藏状态栏 + + + + 非闪烁型游标 + 闪烁型游标 + + + + 矩形 + 下划线 + 垂直条 + + + + 4 x 8 像素 + 6 磅 + 7 磅 + 8 磅 + 9 磅 + 10 磅 + 12 磅 + 14 磅 + 16 磅 + 20 磅 + + + + 白底黑字 + 黑底白字 + 蓝底白字 + 黑底绿字 + 黑底琥珀字 + 黑底红字 + + + + 轨迹球 + \@ 键 + 左 Alt 键 + 右 Alt 键 + 音量 Up 键 + 音量 Down 键 + 相机快门键 + + + + 轨迹球 + \@ 键 + 左 Alt 键 + 右 Alt 键 + 音量 Up 键 + 音量 Down 键 + 相机快门键 + + + + 基于字符 + 基于单词 + + diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml index 6f47b4f0b..9e75415b1 100644 --- a/res/values-zh-rCN/strings.xml +++ b/res/values-zh-rCN/strings.xml @@ -22,6 +22,11 @@ 特殊键 打开/关闭软键盘 + 使用唤醒锁定 + 解除唤醒锁定 + 使用WiFi锁定 + 解除WiFi锁定 + 编辑文本 全部复制 粘贴 @@ -30,7 +35,7 @@ 屏幕 状态栏 - 显示/隐藏状态栏。 + 显示/隐藏状态栏 状态栏 光标样式 @@ -57,6 +62,10 @@ 设置 Ctrl 键 Ctrl 键 + Fn 键 + 设置 Fn 按键 + Fn 键 + 输入方式 选择输入方式或软键盘 输入方式 From e429ad0a5e3d4f0c95ded5693b775bb6f4219840 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20L=C3=B3pez?= Date: Wed, 28 Sep 2011 04:24:51 +0000 Subject: [PATCH 195/847] AndroidTerm: Merge remote branch 'upstream/master' into update Conflicts: AndroidManifest.xml res/layout/term_activity.xml res/values/strings.xml src/jackpal/androidterm/Term.java src/jackpal/androidterm/util/ServiceForegroundCompat.java commit e543ef97835fe089f9f25e6cb4b0ad61d68982c9 Author: Jack Palevich Date: Sun Sep 18 10:13:05 2011 -0700 Generate native code for x86 as well as armeabi. In theory this will allow Android Terminal Emulator to run on an x86 version of Android. commit 7165bc829b573cfd383693fbcfbe0fa85b228b72 Author: Jack Palevich Date: Sun Sep 18 10:11:20 2011 -0700 Update config file for latest Android SDK commit 3a738cdb919f96110bfacacc44be8d9d11741ac7 Author: Jack Palevich Date: Sun Sep 18 10:09:24 2011 -0700 Update private build tool to support NDK r6b commit ccfdb925bf1387b73059d80a57b5a6c5867bf59a Author: jjsan Date: Tue Sep 6 21:36:55 2011 +0200 Updated Slovak Language Change-Id: I7de4ff32182bcba7e0379e3316b05e39a2aa50a8 commit 5238daec51c8c3bd2c1deccf5a04f4930979dbe5 Author: Jack Palevich Date: Mon Aug 29 16:52:45 2011 -0700 Increment version number. commit 811696d75b12d2ff705de83e83550ceb58b238e2 Author: Jack Palevich Date: Sun Aug 28 11:34:21 2011 -0700 Handle fractional-pixel-width fixed-width fonts. This should fix the problems people have been having with text rendering when using non-standard fixed-width fonts. commit f727e5f69edd9a3ac66fcc06c6a6abad681195c1 Author: Jack Palevich Date: Sun Aug 28 10:29:52 2011 -0700 Remove JDK 1.5-incompatible interface method @Override annotations Allows building with JDK 1.5. commit 62c8611fb07c9164fb67ce88c488d7c928a4af37 Author: Steven Luo Date: Sun Aug 28 09:43:09 2011 -0700 Avoid accessing a finished TranscriptScreen when resuming the terminal activity updatePrefs() may ask each terminal session in the list to update its size, which requires accessing the TranscriptScreen. Therefore, we need to update the list of running terminal sessions before calling updatePrefs(), to preclude the possibility of accessing a finished TranscriptScreen. Also, if a window was closed in the WindowList activity, and then the user selected a specific window, the window number passed to us in onActivityResult() refers to the window list *after* taking window closures into account. We therefore need to defer switching to the user's selected window until after the list of running sessions is updated (onActivityResult() is called before onResume(), so it can't be done there); this avoids calling onResume() on a stale EmulatorView, which results in an attempt to access a finished TranscriptScreen. commit 6b70a47d0f7dedee3257056163c09cb3be631a36 Author: Jack Palevich Date: Sat Aug 27 21:43:09 2011 -0700 Fix resource-related problems when running on Android 1.5 to 2.2 Hurray for the emulator for making it easy to test older versions + Use our own copies of system drawables not available in older versions. + Make sure there's a (mdpi resolution) copy of every drawable in the plain "drawable" directory, since that's where Android 1.5 looks. commit 9c11960500eb43050c00883be4d28a68537a4cb7 Author: Steven Luo Date: Sat Aug 27 13:33:30 2011 -0700 Add UI for switching and managing terminal sessions. We expose three different ways to manage terminal sessions: * Options menu items to open a new window and close the current window. * A WindowList activity, reachable from the options menu, which lists all the open windows and allows opening, closing, and switching of windows. The behavior is patterned after the window list activity in the Android browser. * Gestures to change windows -- swipe right-to-left for the next window, left-to-right for the previous window. The options menu items have been reordered to make what should be the five most frequently used items (new window, close window, window list, toggle keyboard, special keys reference) appear in the icon menu so that the user doesn't have to touch "More" to reach them. This patch adds several icons. btn_close_window and ic_menu_windows come from the Android browser source, while the rest come from the Android 2.3.3 SDK platform, revision 2 (platforms/android-10/data/res). commit 5f4114849ba84e8bbab65ca8076915173bb95f7d Author: Steven Luo Date: Sat Aug 27 13:23:52 2011 -0700 Support multiple sessions per terminal activity. This patch does the following: * Replace the EmulatorView widget in the activity layout with a TermViewFlipper, which is a ViewFlipper with a couple of added conveniences for dealing with EmulatorViews (e.g. pausing/resuming them when changing the view). * Add an ArrayList in the service to hold TermSessions, and make the activity bind to the service so that it can retrieve the list. This isn't strictly necessary for multisession support, but allows us to decouple the activity from the sessions, which will (in the future) allow us to restart or even close the activity without losing running sessions. * Now that EmulatorViews aren't being created automatically when the layout is inflated, but are instead created by hand, move a bunch of initialization stuff to the EmulatorView constructor. * Make the input reader threads for each session exit on EOF. These threads hold a reference to their TermSession objects, which would leak otherwise. For now, one EmulatorView and TermSession are created as soon as the activity binds to the service, if none exist; no UI to create additional sessions is included. commit f44119c2118cab693fd0bd67c64e13e0817c9279 Author: Steven Luo Date: Sat Aug 27 13:22:21 2011 -0700 Don't accept input from process after TranscriptScreen is finished. This prevents a bunch of noise in the logs. commit 4ffcbfddc7b8192b73ba4da6dea6560e05676fc0 Author: Steven Luo Date: Sat Aug 27 12:37:47 2011 -0700 Throw away TranscriptScreen backing store on session finish Android's InputMethodManager seems to hold a reference to a finished activity object for a while after the activity is destroyed. This keeps everything the activity holds a reference to in memory, which in our case includes a fairly significant chunk of memory: the backing store to the TranscriptScreen. Throw this away when it's not needed, to reduce the memory impact of this behavior. commit 47cc8a2b6c56ea0214c59445852e63bbfcf030d6 Author: Steven Luo Date: Sat Aug 27 12:33:24 2011 -0700 Use TermSession in activity and view instead of old terminal-handling code This completes the process of isolating the code for each terminal session, which should considerably simplify the task of adding support for multiple sessions. commit bf017dbf1335689c53a9e65d6001806ec2a8819b Author: Steven Luo Date: Sat Aug 27 12:29:49 2011 -0700 Add TermSession class to represent a terminal session At the moment, the assumption that each terminal activity contains only one EmulatorView and hosts only one terminal session is fairly deeply embedded into the code. To help break this assumption, we introduce a new TermSession class, which holds the TerminalEmulator and TranscriptScreen for a session, and handles the launching and watching of the process and the setup of the I/O streams. commit be18807725cddc0f6337a7771b01501a0881e886 Author: Steven Luo Date: Sat Aug 27 12:24:29 2011 -0700 Modify the activity and view to use the new TermSettings class. commit 972a3497781451edde73899969d5ebbe61187c0a Author: Steven Luo Date: Sat Aug 27 12:20:44 2011 -0700 For multisession support, we eventually want to keep the terminal sessions in the service instead of tying them to the activity. This implies that we have to stop holding SharedPreferences objects in all of the classes tied to the session. To this end, introduce a new TermSettings class, which is responsible for reading SharedPreferences and holding the current value of config settings. commit cbd439b630dbf08636b84c9be0c5a81dcfde726b Author: Steven Luo Date: Sat Aug 27 12:12:54 2011 -0700 This patch isn't really necessary, but it makes life easier for me. For local builds, I change the package name to net.steven676.android.terminal, which allows co-installation with the Market version; however, if the debug constants stay in the Term class, I have to change every file using them to either belong to the new package, or else import net.steven676.android.terminal.Term. commit ff2dcf634d93a1ead6e595fa64204ba9e8acf34c Author: Steven Luo Date: Sat Aug 27 12:02:31 2011 -0700 The fact that Term.java contains substantially all of the classes of the application clashes with Java's model of one class per file, and is inconvenient when refactoring the code to allow for multiple terminal sessions in the activity. This patch splits out all of the classes into separate files, and groups related classes into packages; no actual changes to the code are made. commit 56dc9597fc73ae8e6b5d32316993dfa8173ab20f Author: Jack Palevich Date: Sat Aug 27 11:38:01 2011 -0700 Ignore local.properties contains machine-specific information, not useful to save in repo. commit 029570ade268791590e6518538e93f4547ef7ecc Author: Jack Palevich Date: Mon Aug 22 20:53:29 2011 -0700 Shrink market icon to match "safe" bounds See http://www.google.com/support/androidmarket/developer/bin/answer.py?answer=1078870 commit dd43ce4ea9e52422e615ed266c1e6b451a41b761 Author: Jack Palevich Date: Mon Aug 22 20:39:40 2011 -0700 Improve market icon. Shinier, and more like a terminal. commit 3f50ffa6a63b04af3e52128f05d56bbcc8a76315 Author: Jack Palevich Date: Sun Aug 21 16:44:04 2011 -0700 Updated translations Thanks to fireb33@gmail.com commit a6f3ac348bcbe64ccadfc4a4962572c1b9ceda02 Author: Ondrej Zima Date: Tue Aug 16 10:27:44 2011 +0200 Added Czech translation. Change-Id: I42fc8a3c56c5ce7fa457a3f1b7f54c875cb9ce2b commit 804f3b5fa145448d89867d8c34e778910bf10b35 Author: Jack Palevich Date: Sat Jun 25 20:11:04 2011 +0800 Increment version number. commit 839ac83fd224db75e9db83f720a59af43093276e Author: Jack Palevich Date: Sat Jun 25 20:09:29 2011 +0800 Update in-app documentation for special keys Contributed by Steven Luo steven+android@steven676.net commit cb368f0f482a0e20ba3096fd90d9810878456434 Author: Jack Palevich Date: Sat Jun 25 20:07:44 2011 +0800 Add F1-F12 and Ins/Del/Home/End keys Contributed by Steven Luo steven+android@steven676.net who writes: The mapping is as follows: Fn-[1-9]: F1-F9 Fn-0: F10 Ctrl-9: F11 Ctrl-0: F12 Fn-I: Ins Fn-X: Del Fn-H: Home Fn-F: End It should be noted that a real VT100 does not have Home/End keys, and as such, the terminfo entry for TERM=vt100 doesn't contain escape codes for Home and End. This bothers some applications, but not others. The codes generated match those sent by the Linux console (TERM=linux), which appears to work fine as a TERM setting since the emulation fixes in 1.0.30. xterm sends different escape codes, though; I left the escape codes at their present values because TERM=xterm causes problems with some applications. Addresses GitHub issue #9. --- I'm unhappy with the reintroduction of different behaviors for the Ctrl key, but I couldn't think of another choice that's guaranteed to make sense on any soft keyboard layout. (Fn-J/K and Fn-(/) are the two other possibilities that occurred to me.) Feel free to change. commit 29e95e964ec41b8c8de1832950b35cb165eba380 Author: Jack Palevich Date: Sat Jun 25 20:03:30 2011 +0800 Fix behavior of Ctrl-[0-9] Patch contributed by Steven Luo. steven+android@steven676.net Steven writes: In xterm on my Debian system, the Ctrl-[0-9] combinations appear to map to the following: Ctrl-2: ^@ (NUL) Ctrl-3: ^[ (ESC) Ctrl-4: ^\ Ctrl-5: ^] Ctrl-6: ^^ Ctrl-7: ^_ Ctrl-8: \177 (DEL) Ctrl-1, Ctrl-9, and Ctrl-0 appear to send 1, 9, and 0 respectively. I have no idea whether anyone actually seriously uses these, but make these key combinations behave in that manner, and remove the bogus Ctrl-[0-9] and superfluous Fn-[234] combos. commit 892831647b99de29af04a953eb78e3be18049049 Author: Jack Palevich Date: Sat Jun 25 20:00:54 2011 +0800 Make Ctrl/Fn-[A-Z] work with uppercase as well as lowercase letters Patch courtesy Steven Luo steven+android@steven676.net Steven says: For Ctrl-[A-Z], this matches the behavior of xterm running on my Debian box; for Fn-[A-Z], there are no possible compatibility issues and it seems like the most natural thing to do. commit 290aaf22e6a2a729a98e5ea4c094d7f16106d008 Author: Jack Palevich Date: Sat Jun 25 19:50:56 2011 +0800 Add link to Downloads page to README file. commit 28025150c0624121c6b1915f1a717118933a1162 Author: Jack Palevich Date: Sat Jun 25 19:45:32 2011 +0800 Enable an initial command string to be passed as part of the intent. Thanks to Christoph Schmidt-Hieber, M.D., c.schmidt-hieber@ucl.ac.uk for the idea and the patch. commit cd9a67afce18d0a51ceff6e9a308f01382ba9e37 Author: Jack Palevich Date: Sat Jun 25 19:35:39 2011 +0800 Update Italian localization Thanks to fireb33@gmail.com commit 9c5e6f7fde932b35dd9ef85d07d9dd759044988d Author: Jack Palevich Date: Sat Jun 18 19:43:58 2011 +0800 Increment version number. commit a6e99b98f39b44b25832051813243876d50a1ff0 Author: Jack Palevich Date: Sat Jun 18 21:16:32 2011 +0800 Implement a "None" setting for control and function keys. Thanks to Eli Grey for the idea. Also made the "Special keys" dialog text localizable. commit c19d57e13d0b30fac4c6f6d931937afb7952b987 Author: Jack Palevich Date: Sat Jun 18 21:11:42 2011 +0800 Rename the rt tool to pushAndRun This tool is used to build and test the terminal emulator. commit 5d499f43141865047382ca1be47e5817374b182f Author: Jack Palevich Date: Sat Jun 18 19:43:22 2011 +0800 Allow application to be installed on the SD Card. commit 81ddd14c9eb46809ffee5d9819559c945a45135c Author: Jack Palevich Date: Sat Jun 18 19:40:54 2011 +0800 Fix insert and delete line code. Thanks to Sam Jacobson for the bug report and fix! commit 4f369e5bd9c178db013f374a0d1484061e204f06 Author: Jack Palevich Date: Sat Jun 18 19:37:15 2011 +0800 Implement missing SGR escape codes Thanks to Sam Jacobson for the bug report! commit c66d078bde89adfd13e4a87eb1c6898a5338de8f Author: Jack Palevich Date: Sat Jun 18 21:17:09 2011 +0800 Escape single quote character so string resource compiles. commit 6ddda130ebde049964b38eeb49204205648014e6 Merge: df515f5 1d8bfb7 Author: Jack Palevich Date: Sat Jun 18 04:02:36 2011 -0700 Merge pull request #20 from cpasmoi/master missing translations commit 8568e0d0f313eb0d9d90fd91ec1bca4c65c90a31 Author: Patrik Kullman Date: Sun Jun 12 09:48:43 2011 +0200 Mark non-translatable strings Change-Id: I923fa5aafb0345fcfba0ad6d6ae01feb9d0f2c48 --- .gitignore | 1 + AndroidManifest.xml | 3 +- README.md | 21 +- default.properties | 2 +- docs/Building.txt | 4 +- docs/Market Icon.psd | Bin 277317 -> 304537 bytes jni/Android.mk | 2 + jni/Application.mk | 2 + local.properties | 10 - proguard.cfg | 8 +- res/drawable-hdpi/btn_close_window.png | Bin 0 -> 1010 bytes res/drawable-hdpi/ic_menu_add.png | Bin 0 -> 2891 bytes res/drawable-hdpi/ic_menu_back.png | Bin 0 -> 1583 bytes .../ic_menu_close_clear_cancel.png | Bin 0 -> 3698 bytes res/drawable-hdpi/ic_menu_forward.png | Bin 0 -> 1580 bytes res/drawable-hdpi/ic_menu_preferences.png | Bin 0 -> 3106 bytes res/drawable-hdpi/ic_menu_windows.png | Bin 0 -> 1550 bytes res/drawable-ldpi/ic_menu_add.png | Bin 0 -> 1580 bytes .../ic_menu_close_clear_cancel.png | Bin 0 -> 1851 bytes res/drawable-ldpi/ic_menu_preferences.png | Bin 0 -> 1601 bytes res/drawable-mdpi/btn_close_window.png | Bin 0 -> 607 bytes res/drawable-mdpi/ic_menu_add.png | Bin 0 -> 2011 bytes res/drawable-mdpi/ic_menu_back.png | Bin 0 -> 1163 bytes .../ic_menu_close_clear_cancel.png | Bin 0 -> 2425 bytes res/drawable-mdpi/ic_menu_forward.png | Bin 0 -> 1163 bytes res/drawable-mdpi/ic_menu_preferences.png | Bin 0 -> 2096 bytes res/drawable-mdpi/ic_menu_windows.png | Bin 0 -> 1234 bytes res/drawable/btn_close_window.png | Bin 0 -> 607 bytes res/drawable/close_background.xml | 20 + res/drawable/ic_menu_add.png | Bin 0 -> 2011 bytes res/drawable/ic_menu_back.png | Bin 0 -> 1163 bytes res/drawable/ic_menu_close_clear_cancel.png | Bin 0 -> 2425 bytes res/drawable/ic_menu_forward.png | Bin 0 -> 1163 bytes res/drawable/ic_menu_preferences.png | Bin 0 -> 2096 bytes res/drawable/ic_menu_windows.png | Bin 0 -> 1234 bytes res/layout/term_activity.xml | 14 +- res/layout/window_list_item.xml | 47 + res/layout/window_list_new_window.xml | 27 + res/menu/main.xml | 20 +- res/values-es/arrays.xml | 106 + res/values-es/strings.xml | 41 +- res/values-fr/arrays.xml | 13 +- res/values-fr/strings.xml | 12 + res/values-it/arrays.xml | 46 +- res/values-it/strings.xml | 44 +- res/values/arrays.xml | 28 + res/values/strings.xml | 16 + src/jackpal/androidterm/EmulatorView.java | 2002 ++++++++ src/jackpal/androidterm/Term.java | 4485 +---------------- src/jackpal/androidterm/TermDebug.java | 52 + src/jackpal/androidterm/TermService.java | 30 +- src/jackpal/androidterm/TermViewFlipper.java | 113 + src/jackpal/androidterm/WindowList.java | 169 + src/jackpal/androidterm/model/Screen.java | 107 + .../androidterm/model/TextRenderer.java | 31 + .../androidterm/model/UpdateCallback.java | 24 + .../androidterm/session/TermSession.java | 273 + .../androidterm/session/TerminalEmulator.java | 1233 +++++ .../androidterm/session/TranscriptScreen.java | 477 ++ src/jackpal/androidterm/util/ByteQueue.java | 120 + .../{ => util}/ServiceForegroundCompat.java | 2 +- .../androidterm/util/TermSettings.java | 170 + tools/build-release | 2 +- tools/{rt => pushAndRun} | 2 +- 64 files changed, 5449 insertions(+), 4330 deletions(-) create mode 100644 jni/Application.mk delete mode 100644 local.properties create mode 100644 res/drawable-hdpi/btn_close_window.png create mode 100644 res/drawable-hdpi/ic_menu_add.png create mode 100644 res/drawable-hdpi/ic_menu_back.png create mode 100644 res/drawable-hdpi/ic_menu_close_clear_cancel.png create mode 100644 res/drawable-hdpi/ic_menu_forward.png create mode 100644 res/drawable-hdpi/ic_menu_preferences.png create mode 100644 res/drawable-hdpi/ic_menu_windows.png create mode 100644 res/drawable-ldpi/ic_menu_add.png create mode 100644 res/drawable-ldpi/ic_menu_close_clear_cancel.png create mode 100644 res/drawable-ldpi/ic_menu_preferences.png create mode 100644 res/drawable-mdpi/btn_close_window.png create mode 100644 res/drawable-mdpi/ic_menu_add.png create mode 100644 res/drawable-mdpi/ic_menu_back.png create mode 100644 res/drawable-mdpi/ic_menu_close_clear_cancel.png create mode 100644 res/drawable-mdpi/ic_menu_forward.png create mode 100644 res/drawable-mdpi/ic_menu_preferences.png create mode 100644 res/drawable-mdpi/ic_menu_windows.png create mode 100644 res/drawable/btn_close_window.png create mode 100644 res/drawable/close_background.xml create mode 100644 res/drawable/ic_menu_add.png create mode 100644 res/drawable/ic_menu_back.png create mode 100644 res/drawable/ic_menu_close_clear_cancel.png create mode 100644 res/drawable/ic_menu_forward.png create mode 100644 res/drawable/ic_menu_preferences.png create mode 100644 res/drawable/ic_menu_windows.png create mode 100644 res/layout/window_list_item.xml create mode 100644 res/layout/window_list_new_window.xml create mode 100644 res/values-es/arrays.xml create mode 100644 src/jackpal/androidterm/EmulatorView.java create mode 100644 src/jackpal/androidterm/TermDebug.java create mode 100644 src/jackpal/androidterm/TermViewFlipper.java create mode 100644 src/jackpal/androidterm/WindowList.java create mode 100644 src/jackpal/androidterm/model/Screen.java create mode 100644 src/jackpal/androidterm/model/TextRenderer.java create mode 100644 src/jackpal/androidterm/model/UpdateCallback.java create mode 100644 src/jackpal/androidterm/session/TermSession.java create mode 100644 src/jackpal/androidterm/session/TerminalEmulator.java create mode 100644 src/jackpal/androidterm/session/TranscriptScreen.java create mode 100644 src/jackpal/androidterm/util/ByteQueue.java rename src/jackpal/androidterm/{ => util}/ServiceForegroundCompat.java (99%) create mode 100644 src/jackpal/androidterm/util/TermSettings.java rename tools/{rt => pushAndRun} (80%) diff --git a/.gitignore b/.gitignore index 4ce94ab5f..db8c7038c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ bin/ docs/Market Icon.png gen/ libs/ +local.properties obj/ .classpath .project diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 6bbe1c253..0329a9bb8 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,6 +1,6 @@ + android:versionName="1.0.32" android:versionCode="33"> @@ -17,6 +17,7 @@ + diff --git a/README.md b/README.md index 3825795cf..a423eac33 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,21 @@ #Android Terminal Emulator -Android Terminal Emulator is a terminal emulator for communicating with the built-in Android shell. Emulates a reasonably large subset of Digital Equipment Corporation VT-100 terminal codes, so that programs like "vi", "Emacs" and "NetHack" will display properly. +Android Terminal Emulator is a terminal emulator for communicating with the built-in Android shell. +Emulates a reasonably large subset of Digital Equipment Corporation VT-100 terminal codes, so +that programs like "vi", "Emacs" and "NetHack" will display properly. -This code is based on the "Term" application which is included in the Android source code release. It's provided as a separate project for the convenience of developers who do not want to deal with installing and building the whole Android source tree. +This code is based on the "Term" application which is included in the Android source code release. +It's provided as a separate project for the convenience of developers who do not want to deal with +installing and building the whole Android source tree. -Although this program does not include a built-in ssh client, it can be used with command-line-based ssh tools such as dropbear. +Got questions? Please check out the +[FAQ](http://github.com/jackpal/Android-Terminal-Emulator/wiki/Frequently-Asked-Questions) +before emailing or adding an issue. Thanks! -Got questions? Please check out the [FAQ](http://github.com/jackpal/Android-Terminal-Emulator/wiki/Frequently-Asked-Questions) before emailing or adding an issue. Thanks! +Please see the +[Recent Updates](http://github.com/jackpal/Android-Terminal-Emulator/wiki/Recent-Updates) +page for recent updates. -Please see the [Recent Updates](http://github.com/jackpal/Android-Terminal-Emulator/wiki/Recent-Updates) -page for recent updates. \ No newline at end of file +Not on Market? Don't want to compile your own version? No problem. A fairly recent version of +Android Terminal Emulator is here: +[Download Prebuilt version](https://github.com/jackpal/Android-Terminal-Emulator/downloads) diff --git a/default.properties b/default.properties index 510b0908b..8010039f3 100644 --- a/default.properties +++ b/default.properties @@ -8,4 +8,4 @@ # project structure. # Project target. -target=android-11 +target=android-10 diff --git a/docs/Building.txt b/docs/Building.txt index 79833be10..1d0251d26 100644 --- a/docs/Building.txt +++ b/docs/Building.txt @@ -35,7 +35,7 @@ Build the shared library: This should chug away for a while and ultimately produce the file -libs/armeabi/libandroidterm.so +libs/armeabi/libandroidterm2.so Create the Eclipse project: @@ -78,4 +78,4 @@ Eclipse. This is handy because it can be scripted. 4) ant debug 5) Connect a device to your machine, or start the emulator 6) adb install -r bin/Term.apk -7) adb shell am start -n jackpal.androidterm/jackpal.androidterm.Term +7) adb shell am start -n jackpal.androidterm2/jackpal.androidterm2.Term diff --git a/docs/Market Icon.psd b/docs/Market Icon.psd index 90b6f7723e9297dc2ccc2f3ef3d65502908d81a0..45379225411aea482b3e9249e93fa64dd4220367 100644 GIT binary patch literal 304537 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 z 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 Zcpa6IXio|9m1wd0n;H#ugBI)t?Y~U5ka7S3 literal 277317 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 J`r-_y{y#r;gUtW{ diff --git a/jni/Android.mk b/jni/Android.mk index 6fc36f040..d875ab819 100644 --- a/jni/Android.mk +++ b/jni/Android.mk @@ -43,6 +43,8 @@ LOCAL_C_INCLUDES += \ # No special compiler flags. LOCAL_CFLAGS += +LOCAL_LDLIBS := -lm -llog -landroid + # Don't prelink this library. For more efficient code, you may want # to add this library to the prelink map and set this to true. However, # it's difficult to do this for applications that are not supplied as diff --git a/jni/Application.mk b/jni/Application.mk new file mode 100644 index 000000000..f037cdb44 --- /dev/null +++ b/jni/Application.mk @@ -0,0 +1,2 @@ +# Build both ARMv5TE and x86 machine code. +APP_ABI := armeabi x86 diff --git a/local.properties b/local.properties deleted file mode 100644 index c98e8c1fa..000000000 --- a/local.properties +++ /dev/null @@ -1,10 +0,0 @@ -# This file is automatically generated by Android Tools. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# -# This file must *NOT* be checked in Version Control Systems, -# as it contains information specific to your local configuration. - -# location of the SDK. This is only used by Ant -# For customization when using a Version Control System, please read the -# header note. -sdk.dir=/Users/jackpal/code/android-sdk-mac_x86 diff --git a/proguard.cfg b/proguard.cfg index 12dd0392c..b1cdf17b5 100644 --- a/proguard.cfg +++ b/proguard.cfg @@ -18,14 +18,18 @@ native ; } --keepclasseswithmembernames class * { +-keepclasseswithmembers class * { public (android.content.Context, android.util.AttributeSet); } --keepclasseswithmembernames class * { +-keepclasseswithmembers class * { public (android.content.Context, android.util.AttributeSet, int); } +-keepclassmembers class * extends android.app.Activity { + public void *(android.view.View); +} + -keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); diff --git a/res/drawable-hdpi/btn_close_window.png b/res/drawable-hdpi/btn_close_window.png new file mode 100644 index 0000000000000000000000000000000000000000..12e5c5522d00313bea50820bc76301e6d0557e67 GIT binary patch literal 1010 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 KJ#HWmrvCxLmdVWk literal 0 HcmV?d00001 diff --git a/res/drawable-hdpi/ic_menu_close_clear_cancel.png b/res/drawable-hdpi/ic_menu_close_clear_cancel.png new file mode 100644 index 0000000000000000000000000000000000000000..4683f61935a92132db1aa9f9394931f24840c8c8 GIT binary patch literal 3698 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 literal 0 HcmV?d00001 diff --git a/res/drawable-hdpi/ic_menu_windows.png b/res/drawable-hdpi/ic_menu_windows.png new file mode 100644 index 0000000000000000000000000000000000000000..0e80009cbc4181f2503205250bb84eb5966a9bea GIT binary patch literal 1550 zcmZ{kYd8}M7{@o6%_Swe2*<75#@aL`x0*|hb=+K8l(!ge@?$V&C+7_CccGC0f;M*yJuvc9>rlaECEMz62@U5ELhcI(6Qk*MH*hCP(;AOJx2 zld~hr`|O9A0=EoJpUs_#^G!5rELRM3eoeA}otQ40qMLCFc^1Y9v`>av}GIY~+;H2_rqQkLek7e_(_{ODD&Ps*tQ41kzErikvYJKZ+zPAeh^9g(sOE)C;-0uyjc$vha=him z;rm&Wzjc4KYedqaR~QKH&FKPF+wRxoDe#EhH+uR+>_dBK*Q7}6+1Pz+5oX%eLOn^Rb5e==5XRyL+5kZnx|AKto^-4c*su zt{2``#Rz6qx9Zh}lLRdN+QAJ*l_6dl21Nj@gUbmW8UNk&sdh~Ib0)bsMfzT&KXJ-ClFyx zETi4Hc3R>JmtDty;|tXm5=3=ozS`^^)++lVmZm%wqT-Q~v4Pwr$nzNUT^=n{g*4aC z{4Jc3TDdI$8=VEMGu^Sjd;&TfqV!Vj)I&li-wrlTXsk(w;+r)-XNIOfsS*!Vdbk~> z-f?;Gy&s&jN+GNa{E!7I6s)NaD5tRqagXkaM(3IjAn7G&PI+-1^AU@j9$3hnf41qT zMf(n72vr~0m=oQhSKWft>I}_SOvmpZ4b>GpxiO+=1WRAck6dM++o5ww5?U%J8dV9j z#fdPakdND9`rEpTX)_hE$4!;9FfVk0rD-f$h-@ut@l)UCG?zaVG>XJ}w;-m!`Na< zTh-k=u+(xJF9~xY+rZNkike>KZNFWghd0XdGgZ8{)yHstS(3-CiaQi6K3R{yA#?Q| ztg1}ck`bBKtJ0CFYG~4B0W{{^%+Hn`*L!>8LeXBx5k*fUqoG%ATF?g#E$sL8%u~7K zU|yD&vm3k3o$%%nX%QL@5NYeIMo#@WrgU@Q`GIAx#nqlJTZB_TG0kksFUcY4-8XH(n3YB;9-ChW?(-4oZj)sYtXt4~7Br8wZTrW2K*O zH`(bpIVgaPw+f8HOATNSN5IYCmSzZ~xA|VHy$7t|`^@0`t>AEkeb0mc1i~VMhy=?2 z2M%eUU;oCN)-MoEJiFLH346e<4;ks@&TXaF*X4bhVl^>yMEK_JHl kMBxFbASu*w4hsmzd*TCvDChWi8|f0j*~!(h&i+iwUjm`UwEzGB literal 0 HcmV?d00001 diff --git a/res/drawable-ldpi/ic_menu_add.png b/res/drawable-ldpi/ic_menu_add.png new file mode 100644 index 0000000000000000000000000000000000000000..89620af8c03d187d47c7630e9c2a257b88e9e176 GIT binary patch literal 1580 zcmZ{kdpy&77{`CLtrkgXHMbPA5eL65%2o}_Fw6atwOqHEYnaPyO9`by7w0q~x8xea z;^@Xf$wEl!GQ}YwNff1+GyQeWALozf`+j|&_xJPq{`0(^vyS%G3UaD)001aZY%HB+ z)Ltn#RCZF=Ic}C=wI9{m5&+86P&nCbUAPS`QbwC!WhD+Kt~6u9E!Z;t$^x-#)h+-4 zbeUpl;cD|}^m1HC|MM-6%*NjS6y%mT?CcRfC4w8TlGmLfkjfLC3ZWpjYj^TF{|5%} zOwc%>g1{&~YgF1>Y5_Cu4cK8X1nZe8v(6hmj=b`bkDwIXg4xph9t*s42dCeP%pR*A zPLbXmYZ7$d&J#4^#|GpFX`&3STi@z|CrA9#M?f$`*kRQXp4~JW4IOnz9hR3Sf{A6v z#5r)n;--O~w9$jb!iA4R=)iOa#~m5iiBZ|PxemQ}DsOdncq+G%HsOBWC6yDuxAS18 zSOJo|eOES~lZ>Z8!jlXJwWn^TmYlZV`k2Qvy?)J3)bQLr2xcn?=$TUd=zy-n6y<%0 zt$%7e_c@=ok7AHJS!5^O_Q}&Hull#h8b$&8Ixu+F4dRD{pW0fMk?tE?0=Mp2RQY`*jPQ^Q<_u8d%9BErzZ5HO|Kyne+ z?YnWms^gZ8y+xrDNT6DX$%H@ zCgXDQ)~}f=I;GL~LufKaoUL(-zZxEBe)jtM zaORpszV67k^ls~%RZfL*y}b|FH*uMHbUIrV!gnj(IxVn1WEXQu&-Pg+RU=~^-wzzu zZTdSd(TMoRZ^o(*oa*_|*gEl2fm6*Mi9#DgK0MX}^2t#xsC==wIsaQjGDY_&efG?s z09o@!(#v_Tkcst9fR3`F{TX6T{5VBQY*^G+wx%^?j7C?zrNVven0=U*ADFCaqE@JL zzcTuJyyV*u`vPdI-!Q2)KAi2@?fj!uw08Jf-~E)~_*LX~s-WX`42MteO*_8>@ItxN z26ZFvhElGy%oZG=cgGZqXnyx2TFZ(eLbQPHLs2>xqtQ#;ARA|=8G4$c&(RIZnazvM zD$O^=A|cC}x^RBv#hmA6fvbXLRxJ#3!^-^ri}uS$X|?w-*kkR8tXH!6GvvCE_EzF_ zyc_FK$Yot4jhQti_GwD@eB|sFevmt?zJ&;vKbTt8aPm707hW74W`8Wa6vHDNX9P<$ z^-Pf3$|lOGCbXSz%pxA9XJjworDeS1V#5!o1wp=wBQ&bwA?jfv3`Z|_=$fod)$b`q z^*P?D^hd^pWfMbt)_0g5*pgAl&+_Q7SM^}%vDW-_d{*W%k4qEJCg7V_c@>{Mp@3hg z3ya)a=~|Of7DW}|A6OUIUA{WND8}Lu<3#t*Y^&T=JhlXc#llpZEWPAzP)D&8xWbgMVO16pKUf z3yJ0cpdr>JihWF`HvCJ4g!!=|0psXWxR%VQtQeOt|0u36hXq(LWpW24)R)b2X8AIC T5n|Rp*&~2LwzsSz`6T=U5hc46 literal 0 HcmV?d00001 diff --git a/res/drawable-ldpi/ic_menu_close_clear_cancel.png b/res/drawable-ldpi/ic_menu_close_clear_cancel.png new file mode 100644 index 0000000000000000000000000000000000000000..760b9254d7a05fb214c077e959d09c60a8e193b1 GIT binary patch literal 1851 zcmZ{lXE@t?8^`}g)v9rM)M=5b7AML%C2-oLA?0@w~Y2&-MF#?(cQKywmJ#EckdNc>n<5v$8aGU{UeB zxY$`8$!#ah0-G<=!W00iA1Y|EUfiLUuHh_N{$1unL5Am_k)cL77XQuycQ~c80Dyzv z%GAioa$vN8f}h5T4#3CvPPT){gdHR~a3jV&z|~c`sWBd#VMHXBBRO7m`)xka17>Vk7nBKQ-m@*FxqH@@W^kP?Uddv6Q<+8{)b0*H69`Ze7VnA7oJw3&*)=0( zbGSi}`kOz+-Xq3xR@oj~(K^Q7FBc0Q=FHTO3lcjWjG@o`L*hL--OMix5Cbs#CDs!r zIYt7@cjHxK(xg_30wf`0{q6~ClY_?{{X#MYMo>p8)m}^g(R5&_*xs|pXOSVctJF40 zm%@3Ixgk)?yfh!n!Z7}=mR(m5o>eMH$j*4S`^W*=X zVp}$v-{afWy@Nu$6A9jUH|tdsS5XgELNi^rGWgn36)gu4IPzYERHY5IU2D+TLAv8I zIG4YL*w5>FgxIQiNKF)tr5B55h~sJ_ZG@v}3^HS1LwK)YslMRe$BK=YuFH6!`&b|V?#N9Oz*m=r zO&vC(YV31H=zJUAw($1u&f(PP*IhrpmF6Egcn9elddz{rryq{=otzFl)wU!I9{D&3 zoxN>KQRr%znArYHK|$k)vfy9Veha>{y}f@XIlCE07mvE5ivlgHMkZOC-Z$tiwhz2c z|N11)d4b;WVi-zl%ziTUC5D7Q3+seN36-tPKk7|Rlf@Jsd&_b!F}A-R`Fy<@Rj&0i z1YF+C$OUn$9EYt1$puZoc72%|3-9tmXzq&IoGplrCvY57z#7JA`s)-$?Hqursj8<; z2HQ`mTng-DtFFu`vEi=q&CL%lmgJ2+UQRlknohqSz9dQYJeYnpMD@7iYh~1`{OW;s zlpt~4*Sk-*{QC3U(|8WcGHTE*A)5W6{9y`gX2HE)&3+zy}gR1C~^4;z~WD=}@ku~o)d`lBZ9_vE) z{$^W47A={A^YV$kR8{sEx)yKofSo6$M=K*V5xW-d)IGqNIUI{6vZKY*d&;b``;Xfa zz&GzTc8riOs=NIw6B!XSs!qm5&SW0?Gu_8y#J24v}>y*~Ul*a#< zKVMBFzTuuRPPu|u*>(e${#oVq6wkBj0VV<)zYC2$3_=)2AOC>E-_Ab|MHn$pMKW3x z$|W$+w3J^DU-zis5{N%cxtl&YZ-1CTxkn%)khRtp*Q0bBH2Q7`X6SF`^^lQVlQvt^ zge|BYnm(OGQE-No!=<;29O>l#&x<@w&~Rgj_m0N5Pdi8JbG3hNwkFk?aFXvkc5b7S zY9%gqb}PLkNsY(Ahi+gfb(HxD$^rev-9AE33|uk)OPEkGXf1!{_Ba&RvEI~I*{2xH zkKWHuj!lqs$a6rpslglUA3vWuT^@)NR8zlxwwhla_BTw}E3(u$$A#SNo8;{7Pfg&!J|>QXPUJW51)P zAjo3N+#J;xuIy`aiR>-O>({L1JIZCaDA z&04eTCadICL%3%z=jNA^OOUN1k>;uro8nrJ?BkI}SHD|(P+oxr$ccAFfri}4@V}cb zq#cM-M!q{=q|1`UxoMWRVJnIInA4pnSL>FuNW0Gp8_Jn9KRv0#Bp0FF>ft8srt}8TLk+(V! zE`JbWtameno9$3oi5=2U!~*1ik{tQRaN1<$!cV>%fiFtNbbUrxXF`c&h9dc+Nf?-4 z7={HvOB1B21_G&RYB_;GFikK_TSrY(52mS^BUU~8KY?I^e;_vc{|6QkLg$A_;Cur% z5KqE{!F=%%VSuJO$dQDLWhwrDC_KRz6ApkQs<`A?M&jH!60oEwbQlIO@@L5_R>5c- Z#sP!&k0uUb3|K>em6?rcy|K^z{{l+mUl;%Y literal 0 HcmV?d00001 diff --git a/res/drawable-ldpi/ic_menu_preferences.png b/res/drawable-ldpi/ic_menu_preferences.png new file mode 100644 index 0000000000000000000000000000000000000000..efc2f3e4597e6f8aab0ac8e4156e83e999a55d01 GIT binary patch literal 1601 zcmZ{kYdjNr7{`|qRvV2-R;E-g(I(=|Wz=dL*}){!ZORD4u!Y>pNfXmlLyV}9$>pG$ zTckKks$qmN`A|)AH5$v|IbUM0N0!^H&xSsieaK02DQF zXa{#Je=?m&$vL@|A1@gSX`E&HQCR$&xp+sKFmm8lRNw@dBWUTRB*R@pf78oUNSRsta21E zP2E``UHuwA)jzyCn|IhFE)E6WQ#eS|9b%rhdFCINWYN3GZ$<3VFay{H=QV(^$ktlhcC034PYt z&7#B(KKrqM08IASt;lxxo!j=P_@JdMsZeUwYSF_;>_#z0^`-@hKswlTT-=_&EiwcD zA-om+)%_-RS1M%IrbcojuqaT8neULB_Qtd{ZVdTjQgZxYJ3kXym%d#kH{k>ddZ=97 z&l}1jz9Gkn!#FvS$2SuA*L8Fs^GJB3wt2>*l`L7$BVbi)o7@M;Ud&n)$`1DD-(#f%(em`hy4VmY2ehcsEaqebcVXqv& zcCbmqC(>6;9GtcP6Gp122@m935`;LT&X(**uckv}+#-JDx2V;48-qs7)+n`an3bUZUM>Z>7K+th28FTo zA=-GY!4!2r)q6jJo>%!g$r=ZrfrHYq%HO}UIOlkz16ZHgk+dA8*aRriP=w1viJ&k& zh0d0Dt{6~TTBm0NDL}h@@I_}!Jd)`mx%v5~jRnxO%$qQeT?lh(;Upz<2@VK}qI+4Z zq>;26bh6t&J0>)0sfpQvC%hxG=oA6ViCwIiKD-$r$*ER5OeGq|#KjeiIC>SL8@lFn z?c25xg%S87U25h)&kZMZ>s#ewSY@E~*}j2o&N21(Mxk`Or*kDc$hYtAt+aj>`rMxs zcaEN4c`P^U&cz~f>B2r+lw&48cCVF@Rd7@H9HX9Uj>sY0`vBTZ%c(8t+?m9wo#o}d z06U)eFb)PoGDsd)X%Po8`|?Ij1Abn6ek$5<-E^~ShGmWER*tt0Yrd0Qx7BoV9UJPb zkh@Cl& z3_C*B&avv+248;)jU0wNPoaka;HC%y%`Zl-!2eQ|kn`klfH}Qbd6(R1uNgv!FD=45 jj0|w_kxLjZ$lH(XM)vlJ3T-3X$cF&9qj+@rVN$|Bz|ZR- literal 0 HcmV?d00001 diff --git a/res/drawable-mdpi/btn_close_window.png b/res/drawable-mdpi/btn_close_window.png new file mode 100644 index 0000000000000000000000000000000000000000..53f878beba0525e0925a9cc70084b29a41a56446 GIT binary patch literal 607 zcmV-l0-*hgP)N?f=oW+!vdB6=Q8IJFg5OtMBwN8Yxou5XZH*FvAZro!`3bU^@hyoE z%++B)NJ@zKIk)))4hQvuAh=1A)D&?U4Pcpp&k=iClH@C66Bv~dgqK-`bx_&rkwix?7mj3MS! zJNSYFTs8u*b-mea_LLP27I6g>1;>Cd3vAg4Kms25zCY9jz`SVkp-VWkEkpP+N8P#+5z(V5 z;Y^7M-)$NZviLNLqBldSodCKLg)ca4Tk-NR8jVJG>S07p=3G6DFei(E4}7;dj2ylP z^g%>N><1mk58Phm4F9bZticAh9K6pQjxLVS-BkESROkP&qg+T5!k2O(!H(@I7ZTWh t;zFX0QzGEcuVkY7WmF0H_X2(d7yuG7r4|#LmWluX002ovPDHLkV1j>y18o2R literal 0 HcmV?d00001 diff --git a/res/drawable-mdpi/ic_menu_add.png b/res/drawable-mdpi/ic_menu_add.png new file mode 100644 index 0000000000000000000000000000000000000000..57a4099bbb199afe92a9fb3feefc4125190c27c6 GIT binary patch literal 2011 zcmZ{lX*|?x8^`}MVa77d#9&4-wk)B)nbBx+tTXnVp>iz4OdNZbN=ieBl&H{R=d{p* zEQ!O2I)(tlHj*!XZ5zh0)J_@pHxqR(@ffmHCGRul zS=WjhC?l%ttFx||RjANZbn6aMT`1a*29i=PmFD!DvxHK+>?397C6=2{^^S+EwEsom z&N%K{>v`rFWingWw^6Y2ZWCI>x#tr!>-DMBN1@&EV)(~NTC=4_zc7x1knJ>z!6k2( zOU|2050~Tz8w}x6Dh6raHS%9|7j;~im^{fXS=HnKkrk0n+tXRj|Ev^bE{0*aXWuF- zxST%$v_%nTOvru~J#BGD;yJFtmmu+(z3+F`#q)Zbu!)Cbu6XHRy0zzazLCwJa^7t>h|hExD~@C9mwwy68AGXo*@Vgsqj7mYT0U zJd&+~1n6{n!CjAhlY8eEKSp$pQTsZD@)et4QUlny*0n{A_B$=Cxhouu{Pc=gApb$L0tUvbm#^razTeRRu zIvtDun5*3{m-?8Ow-d3@0m?Y7Ebon}+8ka-!nvPY?;g*Z*)+VEc9<3r@`;PK(w-j5 z0uf>^*uF`NwBR#UItLhKnwxV_R0e|(yWp_{4!z~QKu!;$YP+7QQysb~bpy*)jS$J! zyFPEfp%-9OoG{#Q6MmF3?sBY%HxQks()S}o9txfIKTR_68te_x%uztX_VY(dn<R5(Vu0D)AX%E_*~O_Kr3CSNpdRYE+w`!mCB8=bM^-AI*wH1VR$4Go{qMH?0Dk+5pWxj};;VFDG66)O%ypMDg} z8W0;keY5c_Q@{SnyzEIP8)s;3eYMvEm>hpg^s*>MTKMd1$DJ%!cyeen>~>+L6nq6w zdwJ%s26(30XxRxlUVrLz^2(_2dgn!UgwqJPm)9?ni64XBOY`6C%)PGwb0{;zr1PH^ zZ>Za5_|xGsMss)ALOfETkCT=SCxR0+Aw&jS7HhjCWKh(x>%Jmq6`AgE*=|JbRO{j# zK)CRf#w;!|oi}bIB{=;Ickx(#0+oOel&fR67iWSVOuYk?NXm|k))Ax~!TV+&RJm=v zW@~#TJbM%!u|ELr-CvWobf!y5)zdZ_)m^~g!?)S$!JO8E#F2}xxq3kc5Kn_WnFFRe z@}5*aL`ho@OsNb%y_!&$xv(cRQj=e4m{_ahF>#tPfesJnX%Bi1C7Giu(*gP9tnCjN zq{7V?a|F+^$MNeXH_1Zato`FQH@u79LScRFTEWfg`UB2@@+$~zHu~-u9+v&IMQbC_ zdrSSnZl41dJyOfKLBvAWpTsS0H8sBQxA-uP&TJ?ITfWgphvVM+E!j{12$|V$JWjD= zmRG)kzxhBINxb(h`SzQ|cXmCt-l%m{`UNEXl8iO(g-+>N+Pn32+9{p=My)PmTBHv- zDBzHu&;CWwr|eXft50ji^mhHrf&{E2RMr$_)^3&HG?^hi>>bS~A4`oJV8JYFZ!8aQ z{1)0CFjSd)xMY{FN#nE1g9iHA^-Q5H7adH#gp@sxJcsNr?q2g)--eS)^*c`hA@1Iv zLqVlnrCT+vKjwI<*S)$3W5mQ^Z9f10L2gi&mSGB1@9#|+&8Q&7sdd}A)44;OmHUcl zFtyeB7H@)jWyUx~sBwYdjH$;^kHj$O zF-&qm6jJ~I59))uprNil(H&196AqC#0gWyZV*UJA1ghep^I3D-^1GUbsa)N-= zuMNll2#sMzk^MtsqX1A(-z_HatUyWrmkN#WXGR0W*!yr5fx-MTZV|_0PSK;7fEhy| eaW-M}K&C5`&WJm~VHydB02`_!<*~V6(tiLM5`eJ) literal 0 HcmV?d00001 diff --git a/res/drawable-mdpi/ic_menu_back.png b/res/drawable-mdpi/ic_menu_back.png new file mode 100644 index 0000000000000000000000000000000000000000..91fa3e74ac7661023f942cc6421ae1c1e529cf46 GIT binary patch literal 1163 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}a}trX+877Y2q^y~;*F9%q3^WHFHT z0Ash4*>)hovI56+APv^baPD1CI0FN7oTrOpNW|f{(*iT5g-RT+KX3W`cS+usvqe^S zr_5N^cvnOuwnFr%k+M>ZiuG#=Eq?$wyR zl=Fs}@R!^JWyZ;O|8uO_UZ58F{=r*m-}}sa9wa=!)V6NA)|BP0pYK^OeYrb8>*kYt zJ+t2L{Zd((`!+OBHvGR+0`v2XDQVZmY=2kyU;i`fQ;3#g%!x;5R38^zlz(h^ZI)47 zr&+FSb?roLuJAc3eCqL^PX{)oo#0iTa%!#DjAaMR*CcJu4Gj8GB(JNes!_b~;#*8Z62HQ#{K^MdiE@beo=6<6P0kUZV`To+%^wE4`g zHy)L_>uT?RF{k>u&HEgo_JAOhG85kx*JE>cEl{aXsO|s)f6m`+-|qbT_xt~sV+vi;JJ;?@ zkx+j)e=_H-a>>eo(@DiA1pB)~ijS=OT5CS{&gJci9-K+;?Q&~(S(|>WI;+3zdZ4el z_Fu-&Ujv*Ut3R)=F1^quuw?(5>L*L)H!u2C-k)s2skLxo*|JXI1CEwY9t6~8ze=sG z`|!gl!Zfq*(Tqpk-wb5eTsFRF_x*^wcG&$-leJ|fg2htjwkib29i1b=VpnouYf1R^ zXwg&Bmw&u?-WlKcSFFxouHeJf-ev3-yfQO)%Ii1i2Yx#l_u#MORm}wrO#iBXaGU
Xshs?z)M5sc z(iI#^Kp9DpOmKc$NqJ&XDuZJRP*TA&H!(dmC^a#qvhZZ84Nwt-r>mdKI;Vst0PX?+ A+W-In literal 0 HcmV?d00001 diff --git a/res/drawable-mdpi/ic_menu_close_clear_cancel.png b/res/drawable-mdpi/ic_menu_close_clear_cancel.png new file mode 100644 index 0000000000000000000000000000000000000000..04a76b5890be2084e9626754d949954e5a8ad011 GIT binary patch literal 2425 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 X6H_V+Po~-c6)||a`njxgN@xNA*9RGP literal 0 HcmV?d00001 diff --git a/res/drawable/btn_close_window.png b/res/drawable/btn_close_window.png new file mode 100644 index 0000000000000000000000000000000000000000..53f878beba0525e0925a9cc70084b29a41a56446 GIT binary patch literal 607 zcmV-l0-*hgP)N?f=oW+!vdB6=Q8IJFg5OtMBwN8Yxou5XZH*FvAZro!`3bU^@hyoE z%++B)NJ@zKIk)))4hQvuAh=1A)D&?U4Pcpp&k=iClH@C66Bv~dgqK-`bx_&rkwix?7mj3MS! zJNSYFTs8u*b-mea_LLP27I6g>1;>Cd3vAg4Kms25zCY9jz`SVkp-VWkEkpP+N8P#+5z(V5 z;Y^7M-)$NZviLNLqBldSodCKLg)ca4Tk-NR8jVJG>S07p=3G6DFei(E4}7;dj2ylP z^g%>N><1mk58Phm4F9bZticAh9K6pQjxLVS-BkESROkP&qg+T5!k2O(!H(@I7ZTWh t;zFX0QzGEcuVkY7WmF0H_X2(d7yuG7r4|#LmWluX002ovPDHLkV1j>y18o2R literal 0 HcmV?d00001 diff --git a/res/drawable/close_background.xml b/res/drawable/close_background.xml new file mode 100644 index 000000000..7b266e932 --- /dev/null +++ b/res/drawable/close_background.xml @@ -0,0 +1,20 @@ + + + + + + + diff --git a/res/drawable/ic_menu_add.png b/res/drawable/ic_menu_add.png new file mode 100644 index 0000000000000000000000000000000000000000..57a4099bbb199afe92a9fb3feefc4125190c27c6 GIT binary patch literal 2011 zcmZ{lX*|?x8^`}MVa77d#9&4-wk)B)nbBx+tTXnVp>iz4OdNZbN=ieBl&H{R=d{p* zEQ!O2I)(tlHj*!XZ5zh0)J_@pHxqR(@ffmHCGRul zS=WjhC?l%ttFx||RjANZbn6aMT`1a*29i=PmFD!DvxHK+>?397C6=2{^^S+EwEsom z&N%K{>v`rFWingWw^6Y2ZWCI>x#tr!>-DMBN1@&EV)(~NTC=4_zc7x1knJ>z!6k2( zOU|2050~Tz8w}x6Dh6raHS%9|7j;~im^{fXS=HnKkrk0n+tXRj|Ev^bE{0*aXWuF- zxST%$v_%nTOvru~J#BGD;yJFtmmu+(z3+F`#q)Zbu!)Cbu6XHRy0zzazLCwJa^7t>h|hExD~@C9mwwy68AGXo*@Vgsqj7mYT0U zJd&+~1n6{n!CjAhlY8eEKSp$pQTsZD@)et4QUlny*0n{A_B$=Cxhouu{Pc=gApb$L0tUvbm#^razTeRRu zIvtDun5*3{m-?8Ow-d3@0m?Y7Ebon}+8ka-!nvPY?;g*Z*)+VEc9<3r@`;PK(w-j5 z0uf>^*uF`NwBR#UItLhKnwxV_R0e|(yWp_{4!z~QKu!;$YP+7QQysb~bpy*)jS$J! zyFPEfp%-9OoG{#Q6MmF3?sBY%HxQks()S}o9txfIKTR_68te_x%uztX_VY(dn<R5(Vu0D)AX%E_*~O_Kr3CSNpdRYE+w`!mCB8=bM^-AI*wH1VR$4Go{qMH?0Dk+5pWxj};;VFDG66)O%ypMDg} z8W0;keY5c_Q@{SnyzEIP8)s;3eYMvEm>hpg^s*>MTKMd1$DJ%!cyeen>~>+L6nq6w zdwJ%s26(30XxRxlUVrLz^2(_2dgn!UgwqJPm)9?ni64XBOY`6C%)PGwb0{;zr1PH^ zZ>Za5_|xGsMss)ALOfETkCT=SCxR0+Aw&jS7HhjCWKh(x>%Jmq6`AgE*=|JbRO{j# zK)CRf#w;!|oi}bIB{=;Ickx(#0+oOel&fR67iWSVOuYk?NXm|k))Ax~!TV+&RJm=v zW@~#TJbM%!u|ELr-CvWobf!y5)zdZ_)m^~g!?)S$!JO8E#F2}xxq3kc5Kn_WnFFRe z@}5*aL`ho@OsNb%y_!&$xv(cRQj=e4m{_ahF>#tPfesJnX%Bi1C7Giu(*gP9tnCjN zq{7V?a|F+^$MNeXH_1Zato`FQH@u79LScRFTEWfg`UB2@@+$~zHu~-u9+v&IMQbC_ zdrSSnZl41dJyOfKLBvAWpTsS0H8sBQxA-uP&TJ?ITfWgphvVM+E!j{12$|V$JWjD= zmRG)kzxhBINxb(h`SzQ|cXmCt-l%m{`UNEXl8iO(g-+>N+Pn32+9{p=My)PmTBHv- zDBzHu&;CWwr|eXft50ji^mhHrf&{E2RMr$_)^3&HG?^hi>>bS~A4`oJV8JYFZ!8aQ z{1)0CFjSd)xMY{FN#nE1g9iHA^-Q5H7adH#gp@sxJcsNr?q2g)--eS)^*c`hA@1Iv zLqVlnrCT+vKjwI<*S)$3W5mQ^Z9f10L2gi&mSGB1@9#|+&8Q&7sdd}A)44;OmHUcl zFtyeB7H@)jWyUx~sBwYdjH$;^kHj$O zF-&qm6jJ~I59))uprNil(H&196AqC#0gWyZV*UJA1ghep^I3D-^1GUbsa)N-= zuMNll2#sMzk^MtsqX1A(-z_HatUyWrmkN#WXGR0W*!yr5fx-MTZV|_0PSK;7fEhy| eaW-M}K&C5`&WJm~VHydB02`_!<*~V6(tiLM5`eJ) literal 0 HcmV?d00001 diff --git a/res/drawable/ic_menu_back.png b/res/drawable/ic_menu_back.png new file mode 100644 index 0000000000000000000000000000000000000000..91fa3e74ac7661023f942cc6421ae1c1e529cf46 GIT binary patch literal 1163 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}a}trX+877Y2q^y~;*F9%q3^WHFHT z0Ash4*>)hovI56+APv^baPD1CI0FN7oTrOpNW|f{(*iT5g-RT+KX3W`cS+usvqe^S zr_5N^cvnOuwnFr%k+M>ZiuG#=Eq?$wyR zl=Fs}@R!^JWyZ;O|8uO_UZ58F{=r*m-}}sa9wa=!)V6NA)|BP0pYK^OeYrb8>*kYt zJ+t2L{Zd((`!+OBHvGR+0`v2XDQVZmY=2kyU;i`fQ;3#g%!x;5R38^zlz(h^ZI)47 zr&+FSb?roLuJAc3eCqL^PX{)oo#0iTa%!#DjAaMR*CcJu4Gj8GB(JNes!_b~;#*8Z62HQ#{K^MdiE@beo=6<6P0kUZV`To+%^wE4`g zHy)L_>uT?RF{k>u&HEgo_JAOhG85kx*JE>cEl{aXsO|s)f6m`+-|qbT_xt~sV+vi;JJ;?@ zkx+j)e=_H-a>>eo(@DiA1pB)~ijS=OT5CS{&gJci9-K+;?Q&~(S(|>WI;+3zdZ4el z_Fu-&Ujv*Ut3R)=F1^quuw?(5>L*L)H!u2C-k)s2skLxo*|JXI1CEwY9t6~8ze=sG z`|!gl!Zfq*(Tqpk-wb5eTsFRF_x*^wcG&$-leJ|fg2htjwkib29i1b=VpnouYf1R^ zXwg&Bmw&u?-WlKcSFFxouHeJf-ev3-yfQO)%Ii1i2Yx#l_u#MORm}wrO#iBXaGUXshs?z)M5sc z(iI#^Kp9DpOmKc$NqJ&XDuZJRP*TA&H!(dmC^a#qvhZZ84Nwt-r>mdKI;Vst0PX?+ A+W-In literal 0 HcmV?d00001 diff --git a/res/drawable/ic_menu_close_clear_cancel.png b/res/drawable/ic_menu_close_clear_cancel.png new file mode 100644 index 0000000000000000000000000000000000000000..04a76b5890be2084e9626754d949954e5a8ad011 GIT binary patch literal 2425 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 X6H_V+Po~-c6)||a`njxgN@xNA*9RGP literal 0 HcmV?d00001 diff --git a/res/layout/term_activity.xml b/res/layout/term_activity.xml index 4daf5245d..bce7a128c 100644 --- a/res/layout/term_activity.xml +++ b/res/layout/term_activity.xml @@ -16,15 +16,9 @@ */ --> - - - - - + android:background="@android:color/black" + /> diff --git a/res/layout/window_list_item.xml b/res/layout/window_list_item.xml new file mode 100644 index 000000000..eca28a67e --- /dev/null +++ b/res/layout/window_list_item.xml @@ -0,0 +1,47 @@ + + + + + + + + diff --git a/res/layout/window_list_new_window.xml b/res/layout/window_list_new_window.xml new file mode 100644 index 000000000..a017d3267 --- /dev/null +++ b/res/layout/window_list_new_window.xml @@ -0,0 +1,27 @@ + + + + diff --git a/res/menu/main.xml b/res/menu/main.xml index a777008fa..5c58dd766 100644 --- a/res/menu/main.xml +++ b/res/menu/main.xml @@ -16,16 +16,26 @@ --> + + + + + + android:title="@string/preferences" + android:icon="@drawable/ic_menu_preferences" /> - - android:title="@string/enable_wakelock" /> diff --git a/res/values-es/arrays.xml b/res/values-es/arrays.xml new file mode 100644 index 000000000..81118262f --- /dev/null +++ b/res/values-es/arrays.xml @@ -0,0 +1,106 @@ + + + + + + Mostrar barra de estado + Ocultar barra de estado + + + + Cursor fijo + Cursor parpadeante + + + + Rectángulo + Subrayado + Barra vertical + + + + 4 x 8 pixels + 6 pt + 7 pt + 8 pt + 9 pt + 10 pt + 12 pt + 14 pt + 16 pt + 20 pt + + + + Texto negro sobre blanco + Texto blanco sobre negro + Texto blanco sobre azul + Texto verde sobre negro + Texto ámbar sobre negro + Texto rojo sobre negro + + + + Bola de navegación + Tecla \@ + Alt izquierda + Alt derecha + Subir volumen + Bajar volumen + Tecla de cámara + Ninguna + + + + Bola de navegación + Tecla \@ + Alt izquierda + Alt derecha + Subir volumen + Bajar volumen + Tecla de cámara + Ninguna + + + + Basado en caracteres + Basado en palabras + + + + Bola de navegación + Tecla \@ + Alt izquierda + Alt derecha + Subir volumen + Bajar volumen + Tecla de cámara + Ninguna + + + + Bola de navegación + Tecla \@ + Alt izquierda + Alt derecha + Subir volumen + Bajar volumen + Tecla de cámara + Ninguna + + + diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml index 8070b3cdc..dce968c34 100644 --- a/res/values-es/strings.xml +++ b/res/values-es/strings.xml @@ -17,16 +17,28 @@ Terminal Preferencias - Resetear terminal - Enviar Email + Nueva ventana + Cerrar ventana + Ventanas + Anterior + Siguiente + Cerrar terminal + Enviar correo-e Teclas especiales Activar teclado + Bloquear protector + Liberar protector + Bloquear WiFi + Liberar WiFi + Editar texto Seleccionar texto Copiar todo Pegar + Ejecutando sesión de terminal + Pantalla @@ -35,40 +47,51 @@ Barra de estado Estilo cursor - Escoja estilo de cursor. + Escoge estilo de cursor. Estilo cursor Parpadeo cursor - Escoja parpadeo de cursor. + Escoge parpadeo de cursor. Parpadeo cursor Texto Tamaño Fuente - Elija altura de los carácteres en puntos. + Elige altura de los carácteres en puntos. Tamaño Fuente Colores - Elija color de texto. + Elige color de texto. Color de texto Teclado Tecla Control - Elija tecla de control. + Elige tecla de control. Tecla Control + Tecla Fn + Elige tecla Fn. + Tecla Fn + Método de entrada - Elija estodo de entrada para el teclado. + Elige método de entrada para el teclado. Método de entrada Shell Línea de comandos - Especifique la línea de comandos de shell. + Especifica la línea de comandos de shell. Shell Comando inicial Enviar al shell cuando se inicia. Comando inicial + Teclas Control y Función + CTRLKEY Espacio : Ctrl-@ (NUL)\nCTRLKEY A..Z : Ctrl-A..Z\nCTRLKEY 5 : Ctrl-]\nCTRLKEY 6 : Ctrl-^\nCTRLKEY 7 : Ctrl-_\nCTRLKEY 9 : F11\nCTRLKEY 0 : F12 + Tecla Control no establecida. + + FNKEY 1..9 : F1-F9\nFNKEY 0 : F10\nFNKEY W : Arriba\nFNKEY A : Izquierda\nFNKEY S : Abajo\nFNKEY D : Derecha\nFNKEY P : RePág\nFNKEY N : AvPág\nFNKEY T : Tab\nFNKEY L : |\nFNKEY U : _\nFNKEY E : Ctrl-[ (ESC)\nFNKEY X : Supr\nFNKEY I : Insert\nFNKEY H : Inicio\nFNKEY F : Fin\nFNKEY . : Ctrl-\\\n + Tecla Fn no establecida. + diff --git a/res/values-fr/arrays.xml b/res/values-fr/arrays.xml index a7c51b72c..1dad65bb7 100644 --- a/res/values-fr/arrays.xml +++ b/res/values-fr/arrays.xml @@ -48,10 +48,21 @@ Touche Alt droite Touche Vol Haut Touche Vol Bas + Touche Camera + + + + Trackball + Touche \@ + Touche Alt gauche + Touche Alt droite + Touche Vol Haut + Touche Vol Bas + Touche Camera Par caractère Par mot - + \ No newline at end of file diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml index e133ae518..5c3de7e6b 100644 --- a/res/values-fr/strings.xml +++ b/res/values-fr/strings.xml @@ -22,10 +22,18 @@ Touches spéciales Afficher/Masquer Clavier + Activer WakeLock + Désactiver WakeLock + Activer WifiLock + Désactiver WifiLock + Modifier le texte + Selectionner le texte Tout copier Coller + Une session terminal est en cours d\'exécution + Écran @@ -57,6 +65,10 @@ Choisir quelle touche utiliser pour control (CTRL) Touche CTRL + Touche Fn + Choisir quelle touche utiliser pour Fonction (Fn) + Touche Fn + Méthode d\'entrée Choisir la méthode d\'entrée du clavier virtuel Méthode d\'entrée diff --git a/res/values-it/arrays.xml b/res/values-it/arrays.xml index 1c0c48cda..25a663b05 100644 --- a/res/values-it/arrays.xml +++ b/res/values-it/arrays.xml @@ -22,7 +22,7 @@ - Cursore non lampeggiante + Cursore fisso Cursore lampeggiante @@ -56,11 +56,24 @@ Trackball - Tasto \@ - Tasto Alt sin. - Tasto Alt des. - Pulsante Vol + - Pulsante Vol - + \@ + Alt sin. + Alt des. + Volume + + Volume - + Fotocamera + Nessuno + + + + Trackball + \@ + Alt sinistro + Alt destro + Volume + + Volume - + Fotocamera + Nessuno @@ -68,4 +81,25 @@ Word-based + + T.ball + \@ + AltSX + AltDX + Vol+ + Vol- + Camera + Ness. + + + + T.ball + \@ + AltSX + AltDX + Vol+ + Vol- + Camera + Ness. + diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml index 91f91a55e..dce03b9cb 100644 --- a/res/values-it/strings.xml +++ b/res/values-it/strings.xml @@ -15,12 +15,12 @@ * limitations under the License. --> - Emulatore Terminale - Preferenze - Resetta term - Invia Email - Combinazione tasti - Attiva/disattiva keyboard + Emulatore terminale + Impostazioni + Reset terminale + Invia email + Tasti speciali + Mostra/nascondi tastiera Attiva WakeLock Disatt. WakeLock @@ -28,14 +28,16 @@ Disatt. WifiLock Modifica testo + Seleziona testo Copia tutto Incolla - + Terminale in esecuzione + Schermo Status bar - Mostra/nascondi status bar. + Mostra o nascondi la status bar. Status bar Stile cursore @@ -43,7 +45,7 @@ Stile cursore Lampeggio cursore - Scegli lampeggio cursore. + Scegli il lampeggio del cursore. Lampeggio cursore Testo @@ -52,22 +54,22 @@ Scegli l\'altezza dei caratteri in punti. Dimensione carattere - Colori + Colore Scegli il colore del testo. Colore testo - Tastierra + Tastiera - Tasto control - Scegli tasto control. - Tasto control + Tasto Control + Scegli il tasto Control. + Tasto Control Tasto Fn - Scegli tasto Fn. + Scegli il tasto Fn. Tasto Fn Metodo inserimento - Scegli il metodo di inserimento per la tastiera virtuale. + Scegli il metodo di inserimento per la tastiera. Metodo inserimento Shell @@ -76,7 +78,15 @@ Shell Comando iniziale - Inviato alla shell al suo avvio. + Inviato alla shell all\'avvio. Comando iniziale + Tasti Control e Fn + + CTRLKEY spazio : Control-@ (NUL)\nCTRLKEY A..Z : Control-A..Z\nCTRLKEY 0..9 : Control-0..9 + Nessun tasto Control impostato. + + FNKEY W : Su\nFNKEY A : Sinistra\nFNKEY S : Giù\nFNKEY D : Destra\nFNKEY P : Pag.Su\nFNKEY N : Pag.Giù\nFNKEY T : Tab\nFNKEY L : | (pipe)\nFNKEY U : _ (underscore)\nFNKEY E : Control-[ (ESC)\nFNKEY . : Control-\\\nFNKEY 2 : Control-_\nFNKEY 3 : Control-^\nFNKEY 4 : Control-] + Nessun tasto Fn impostato. + diff --git a/res/values/arrays.xml b/res/values/arrays.xml index 2ee0630de..3b97d2b4e 100644 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -105,6 +105,7 @@ Vol Up key Vol Down key Camera key + None @@ -116,6 +117,7 @@ 4 5 6 + 7 @@ -126,6 +128,7 @@ Vol Up key Vol Down key Camera key + None @@ -137,6 +140,7 @@ 4 5 6 + 7 @@ -149,4 +153,28 @@ 0 1 + + + + Ball + \@ + Left-Alt + Right-Alt + Vol-Up + Vol-Dn + Camera + None + + + + + Ball + \@ + Left-Alt + Right-Alt + Vol-Up + Vol-Dn + Camera + None + diff --git a/res/values/strings.xml b/res/values/strings.xml index c2173216e..3e0b7a867 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -17,6 +17,11 @@ Terminal Emulator Preferences + New window + Close window + Windows + Prev window + Next window Reset term Email to Special keys @@ -93,4 +98,15 @@ 0 /system/bin/sh - export PATH=/data/local/bin:$PATH + + Control and Function Keys + + CTRLKEY Space : Control-@ (NUL)\nCTRLKEY A..Z : Control-A..Z\nCTRLKEY 5 : Control-]\nCTRLKEY 6 : Control-^\nCTRLKEY 7 : Control-_\nCTRLKEY 9 : F11\nCTRLKEY 0 : F12 + No control key set. + + FNKEY 1..9 : F1-F9\nFNKEY 0 : F10\nFNKEY W : Up\nFNKEY A : Left\nFNKEY S : Down\nFNKEY D : Right\nFNKEY P : PageUp\nFNKEY N : PageDown\nFNKEY T : Tab\nFNKEY L : | (pipe)\nFNKEY U : _ (underscore)\nFNKEY E : Control-[ (ESC)\nFNKEY X : Delete\nFNKEY I : Insert\nFNKEY H : Home\nFNKEY F : End\nFNKEY . : Control-\\\n + No function key set. + diff --git a/src/jackpal/androidterm/EmulatorView.java b/src/jackpal/androidterm/EmulatorView.java new file mode 100644 index 000000000..4afa0a026 --- /dev/null +++ b/src/jackpal/androidterm/EmulatorView.java @@ -0,0 +1,2002 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jackpal.androidterm2; + +import android.R; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.ColorMatrixColorFilter; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.Rect; +import android.graphics.Typeface; +import android.os.Bundle; +import android.os.Handler; +import android.text.ClipboardManager; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.GestureDetector; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; +import android.view.inputmethod.CompletionInfo; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.ExtractedText; +import android.view.inputmethod.ExtractedTextRequest; +import android.view.inputmethod.InputConnection; + +import jackpal.androidterm2.model.TextRenderer; +import jackpal.androidterm2.model.UpdateCallback; +import jackpal.androidterm2.session.TermSession; +import jackpal.androidterm2.session.TerminalEmulator; +import jackpal.androidterm2.session.TranscriptScreen; +import jackpal.androidterm2.util.TermSettings; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * A view on a transcript and a terminal emulator. Displays the text of the + * transcript and the current cursor position of the terminal emulator. + */ +public class EmulatorView extends View implements GestureDetector.OnGestureListener { + + private final String TAG = "EmulatorView"; + private final boolean LOG_KEY_EVENTS = TermDebug.DEBUG && false; + + private TermSettings mSettings; + private TermViewFlipper mViewFlipper; + + /** + * We defer some initialization until we have been layed out in the view + * hierarchy. The boolean tracks when we know what our size is. + */ + private boolean mKnownSize; + + private int mVisibleWidth; + private int mVisibleHeight; + private Rect mVisibleRect = new Rect(); + + private TermSession mTermSession; + + /** + * Our transcript. Contains the screen and the transcript. + */ + private TranscriptScreen mTranscriptScreen; + + /** + * Total width of each character, in pixels + */ + private float mCharacterWidth; + + /** + * Total height of each character, in pixels + */ + private int mCharacterHeight; + + /** + * Used to render text + */ + private TextRenderer mTextRenderer; + + /** + * Text size. Zero means 4 x 8 font. + */ + private int mTextSize; + + private int mCursorStyle; + private int mCursorBlink; + + /** + * Foreground color. + */ + private int mForeground; + + /** + * Background color. + */ + private int mBackground; + + /** + * Used to paint the cursor + */ + private Paint mCursorPaint; + + private Paint mBackgroundPaint; + + private boolean mUseCookedIme; + + /** + * Our terminal emulator. We use this to get the current cursor position. + */ + private TerminalEmulator mEmulator; + + /** + * The number of rows of text to display. + */ + private int mRows; + + /** + * The number of columns of text to display. + */ + private int mColumns; + + /** + * The number of columns that are visible on the display. + */ + + private int mVisibleColumns; + + /** + * The top row of text to display. Ranges from -activeTranscriptRows to 0 + */ + private int mTopRow; + + private int mLeftColumn; + + /** + * Used to receive data from the remote process. + */ + private FileOutputStream mTermOut; + + private static final int SCREEN_CHECK_PERIOD = 1000; + private static final int CURSOR_BLINK_PERIOD = 1000; + + private boolean mCursorVisible = true; + + private boolean mIsSelectingText = false; + + + private float mDensity; + + private float mScaledDensity; + private static final int SELECT_TEXT_OFFSET_Y = -40; + private int mSelXAnchor = -1; + private int mSelYAnchor = -1; + private int mSelX1 = -1; + private int mSelY1 = -1; + private int mSelX2 = -1; + private int mSelY2 = -1; + + /** + * Used to poll if the view has changed size. Wish there was a better way to do this. + */ + private Runnable mCheckSize = new Runnable() { + + public void run() { + updateSize(false); + mHandler.postDelayed(this, SCREEN_CHECK_PERIOD); + } + }; + + private Runnable mBlinkCursor = new Runnable() { + public void run() { + if (mCursorBlink != 0) { + mCursorVisible = ! mCursorVisible; + mHandler.postDelayed(this, CURSOR_BLINK_PERIOD); + } else { + mCursorVisible = true; + } + // Perhaps just invalidate the character with the cursor. + invalidate(); + } + }; + + private GestureDetector mGestureDetector; + private float mScrollRemainder; + private TermKeyListener mKeyListener; + + private String mImeBuffer = ""; + + /** + * Our message handler class. Implements a periodic callback. + */ + private final Handler mHandler = new Handler(); + + /** + * Called by the TermSession when the contents of the view need updating + */ + private UpdateCallback mUpdateNotify = new UpdateCallback() { + public void onUpdate() { + if ( mIsSelectingText ) { + int rowShift = mEmulator.getScrollCounter(); + mSelY1 -= rowShift; + mSelY2 -= rowShift; + mSelYAnchor -= rowShift; + } + mEmulator.clearScrollCounter(); + ensureCursorVisible(); + invalidate(); + } + }; + + public UpdateCallback getUpdateCallback() { + return mUpdateNotify; + } + + public EmulatorView(Context context, TermSession session, TermViewFlipper viewFlipper, DisplayMetrics metrics) { + super(context); + commonConstructor(session, viewFlipper); + setDensity(metrics); + } + + public void setDensity(DisplayMetrics metrics) { + mDensity = metrics.density; + mScaledDensity = metrics.scaledDensity; + } + + public void onResume() { + updateSize(false); + mHandler.postDelayed(mCheckSize, SCREEN_CHECK_PERIOD); + if (mCursorBlink != 0) { + mHandler.postDelayed(mBlinkCursor, CURSOR_BLINK_PERIOD); + } + } + + public void onPause() { + mHandler.removeCallbacks(mCheckSize); + if (mCursorBlink != 0) { + mHandler.removeCallbacks(mBlinkCursor); + } + } + + public void updatePrefs(TermSettings settings) { + mSettings = settings; + setTextSize((int) (mSettings.getFontSize() * mDensity)); + setCursorStyle(mSettings.getCursorStyle(), mSettings.getCursorBlink()); + setUseCookedIME(mSettings.useCookedIME()); + setColors(); + } + + public void setColors() { + int[] scheme = mSettings.getColorScheme(); + mForeground = scheme[0]; + mBackground = scheme[1]; + updateText(); + } + + public void resetTerminal() { + mEmulator.reset(); + invalidate(); + } + + @Override + public boolean onCheckIsTextEditor() { + return true; + } + + @Override + public InputConnection onCreateInputConnection(EditorInfo outAttrs) { + outAttrs.inputType = mUseCookedIme ? + EditorInfo.TYPE_CLASS_TEXT : + EditorInfo.TYPE_NULL; + return new InputConnection() { + private boolean mInBatchEdit; + /** + * Used to handle composing text requests + */ + private int mCursor; + private int mComposingTextStart; + private int mComposingTextEnd; + private int mSelectedTextStart; + private int mSelectedTextEnd; + + private void sendChar(int c) { + try { + mapAndSend(c); + } catch (IOException ex) { + + } + } + + private void sendText(CharSequence text) { + int n = text.length(); + try { + for(int i = 0; i < n; i++) { + char c = text.charAt(i); + mapAndSend(c); + } + mTermOut.flush(); + } catch (IOException e) { + Log.e(TAG, "error writing ", e); + } + } + + private void mapAndSend(int c) throws IOException { + int result = mKeyListener.mapControlChar(c); + if (result < TermKeyListener.KEYCODE_OFFSET) { + mTermOut.write(result); + } else { + mKeyListener.handleKeyCode(result - TermKeyListener.KEYCODE_OFFSET, mTermOut, getKeypadApplicationMode()); + } + } + + public boolean beginBatchEdit() { + if (TermDebug.LOG_IME) { + Log.w(TAG, "beginBatchEdit"); + } + setImeBuffer(""); + mCursor = 0; + mComposingTextStart = 0; + mComposingTextEnd = 0; + mInBatchEdit = true; + return true; + } + + public boolean clearMetaKeyStates(int arg0) { + if (TermDebug.LOG_IME) { + Log.w(TAG, "clearMetaKeyStates " + arg0); + } + return false; + } + + public boolean commitCompletion(CompletionInfo arg0) { + if (TermDebug.LOG_IME) { + Log.w(TAG, "commitCompletion " + arg0); + } + return false; + } + + public boolean endBatchEdit() { + if (TermDebug.LOG_IME) { + Log.w(TAG, "endBatchEdit"); + } + mInBatchEdit = false; + return true; + } + + public boolean finishComposingText() { + if (TermDebug.LOG_IME) { + Log.w(TAG, "finishComposingText"); + } + sendText(mImeBuffer); + setImeBuffer(""); + mComposingTextStart = 0; + mComposingTextEnd = 0; + mCursor = 0; + return true; + } + + public int getCursorCapsMode(int arg0) { + if (TermDebug.LOG_IME) { + Log.w(TAG, "getCursorCapsMode(" + arg0 + ")"); + } + return 0; + } + + public ExtractedText getExtractedText(ExtractedTextRequest arg0, + int arg1) { + if (TermDebug.LOG_IME) { + Log.w(TAG, "getExtractedText" + arg0 + "," + arg1); + } + return null; + } + + public CharSequence getTextAfterCursor(int n, int flags) { + if (TermDebug.LOG_IME) { + Log.w(TAG, "getTextAfterCursor(" + n + "," + flags + ")"); + } + int len = Math.min(n, mImeBuffer.length() - mCursor); + if (len <= 0 || mCursor < 0 || mCursor >= mImeBuffer.length()) { + return ""; + } + return mImeBuffer.substring(mCursor, mCursor + len); + } + + public CharSequence getTextBeforeCursor(int n, int flags) { + if (TermDebug.LOG_IME) { + Log.w(TAG, "getTextBeforeCursor(" + n + "," + flags + ")"); + } + int len = Math.min(n, mCursor); + if (len <= 0 || mCursor < 0 || mCursor >= mImeBuffer.length()) { + return ""; + } + return mImeBuffer.substring(mCursor-len, mCursor); + } + + public boolean performContextMenuAction(int arg0) { + if (TermDebug.LOG_IME) { + Log.w(TAG, "performContextMenuAction" + arg0); + } + return true; + } + + public boolean performPrivateCommand(String arg0, Bundle arg1) { + if (TermDebug.LOG_IME) { + Log.w(TAG, "performPrivateCommand" + arg0 + "," + arg1); + } + return true; + } + + public boolean reportFullscreenMode(boolean arg0) { + if (TermDebug.LOG_IME) { + Log.w(TAG, "reportFullscreenMode" + arg0); + } + return true; + } + + public boolean commitCorrection () { + if (TermDebug.LOG_IME) { + Log.w(TAG, "commitCorrection"); + } + return true; + } + + public boolean commitText(CharSequence text, int newCursorPosition) { + if (TermDebug.LOG_IME) { + Log.w(TAG, "commitText(\"" + text + "\", " + newCursorPosition + ")"); + } + clearComposingText(); + sendText(text); + setImeBuffer(""); + mCursor = 0; + return true; + } + + private void clearComposingText() { + setImeBuffer(mImeBuffer.substring(0, mComposingTextStart) + + mImeBuffer.substring(mComposingTextEnd)); + if (mCursor < mComposingTextStart) { + // do nothing + } else if (mCursor < mComposingTextEnd) { + mCursor = mComposingTextStart; + } else { + mCursor -= mComposingTextEnd - mComposingTextStart; + } + mComposingTextEnd = mComposingTextStart = 0; + } + + public boolean deleteSurroundingText(int leftLength, int rightLength) { + if (TermDebug.LOG_IME) { + Log.w(TAG, "deleteSurroundingText(" + leftLength + + "," + rightLength + ")"); + } + if (leftLength > 0) { + for (int i = 0; i < leftLength; i++) { + sendKeyEvent( + new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)); + } + } else if ((leftLength == 0) && (rightLength == 0)) { + // Delete key held down / repeating + sendKeyEvent( + new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)); + } + // TODO: handle forward deletes. + return true; + } + + public boolean performEditorAction(int actionCode) { + if (TermDebug.LOG_IME) { + Log.w(TAG, "performEditorAction(" + actionCode + ")"); + } + if (actionCode == EditorInfo.IME_ACTION_UNSPECIFIED) { + // The "return" key has been pressed on the IME. + sendText("\n"); + } + return true; + } + + public boolean sendKeyEvent(KeyEvent event) { + if (TermDebug.LOG_IME) { + Log.w(TAG, "sendKeyEvent(" + event + ")"); + } + // Some keys are sent here rather than to commitText. + // In particular, del and the digit keys are sent here. + // (And I have reports that the HTC Magic also sends Return here.) + // As a bit of defensive programming, handle every key. + dispatchKeyEvent(event); + return true; + } + + public boolean setComposingText(CharSequence text, int newCursorPosition) { + if (TermDebug.LOG_IME) { + Log.w(TAG, "setComposingText(\"" + text + "\", " + newCursorPosition + ")"); + } + setImeBuffer(mImeBuffer.substring(0, mComposingTextStart) + + text + mImeBuffer.substring(mComposingTextEnd)); + mComposingTextEnd = mComposingTextStart + text.length(); + mCursor = newCursorPosition > 0 ? mComposingTextEnd + newCursorPosition - 1 + : mComposingTextStart - newCursorPosition; + return true; + } + + public boolean setSelection(int start, int end) { + if (TermDebug.LOG_IME) { + Log.w(TAG, "setSelection" + start + "," + end); + } + int length = mImeBuffer.length(); + if (start == end && start > 0 && start < length) { + mSelectedTextStart = mSelectedTextEnd = 0; + mCursor = start; + } else if (start < end && start > 0 && end < length) { + mSelectedTextStart = start; + mSelectedTextEnd = end; + mCursor = start; + } + return true; + } + + public boolean setComposingRegion(int start, int end) { + if (TermDebug.LOG_IME) { + Log.w(TAG, "setComposingRegion " + start + "," + end); + } + if (start < end && start > 0 && end < mImeBuffer.length()) { + clearComposingText(); + mComposingTextStart = start; + mComposingTextEnd = end; + } + return true; + } + + public CharSequence getSelectedText(int flags) { + if (TermDebug.LOG_IME) { + Log.w(TAG, "getSelectedText " + flags); + } + return mImeBuffer.substring(mSelectedTextStart, mSelectedTextEnd+1); + } + + }; + } + + private void setImeBuffer(String buffer) { + if (!buffer.equals(mImeBuffer)) { + invalidate(); + } + mImeBuffer = buffer; + } + + public boolean getKeypadApplicationMode() { + return mEmulator.getKeypadApplicationMode(); + } + + private void commonConstructor(TermSession session, TermViewFlipper viewFlipper) { + mTextRenderer = null; + mCursorPaint = new Paint(); + mCursorPaint.setARGB(255,128,128,128); + mBackgroundPaint = new Paint(); + mTopRow = 0; + mLeftColumn = 0; + mGestureDetector = new GestureDetector(this); + // mGestureDetector.setIsLongpressEnabled(false); + setVerticalScrollBarEnabled(true); + setFocusable(true); + setFocusableInTouchMode(true); + + initialize(session, viewFlipper); + } + + @Override + protected int computeVerticalScrollRange() { + return mTranscriptScreen.getActiveRows(); + } + + @Override + protected int computeVerticalScrollExtent() { + return mRows; + } + + @Override + protected int computeVerticalScrollOffset() { + return mTranscriptScreen.getActiveRows() + mTopRow - mRows; + } + + /** + * Call this to initialize the view. + * + * @param session The terminal session this view will be displaying + */ + private void initialize(TermSession session, TermViewFlipper viewFlipper) { + mTermSession = session; + mTranscriptScreen = session.getTranscriptScreen(); + mEmulator = session.getEmulator(); + mTermOut = session.getTermOut(); + + mViewFlipper = viewFlipper; + + mKeyListener = new TermKeyListener(); + mTextSize = 10; + mForeground = TermSettings.WHITE; + mBackground = TermSettings.BLACK; + updateText(); + + requestFocus(); + } + + public TermSession getTermSession() { + return mTermSession; + } + + /** + * Page the terminal view (scroll it up or down by delta screenfulls.) + * + * @param delta the number of screens to scroll. Positive means scroll down, + * negative means scroll up. + */ + public void page(int delta) { + mTopRow = + Math.min(0, Math.max(-(mTranscriptScreen + .getActiveTranscriptRows()), mTopRow + mRows * delta)); + invalidate(); + } + + /** + * Page the terminal view horizontally. + * + * @param deltaColumns the number of columns to scroll. Positive scrolls to + * the right. + */ + public void pageHorizontal(int deltaColumns) { + mLeftColumn = + Math.max(0, Math.min(mLeftColumn + deltaColumns, mColumns + - mVisibleColumns)); + invalidate(); + } + + /** + * Sets the text size, which in turn sets the number of rows and columns + * + * @param fontSize the new font size, in pixels. + */ + public void setTextSize(int fontSize) { + mTextSize = fontSize; + updateText(); + } + + public void setCursorStyle(int style, int blink) { + mCursorStyle = style; + if (blink != 0 && mCursorBlink == 0) { + mHandler.postDelayed(mBlinkCursor, CURSOR_BLINK_PERIOD); + } else if (blink == 0 && mCursorBlink != 0) { + mHandler.removeCallbacks(mBlinkCursor); + } + mCursorBlink = blink; + } + + public void setUseCookedIME(boolean useCookedIME) { + mUseCookedIme = useCookedIME; + } + + // Begin GestureDetector.OnGestureListener methods + + public boolean onSingleTapUp(MotionEvent e) { + return true; + } + + public void onLongPress(MotionEvent e) { + showContextMenu(); + } + + public boolean onScroll(MotionEvent e1, MotionEvent e2, + float distanceX, float distanceY) { + distanceY += mScrollRemainder; + int deltaRows = (int) (distanceY / mCharacterHeight); + mScrollRemainder = distanceY - deltaRows * mCharacterHeight; + mTopRow = + Math.min(0, Math.max(-(mTranscriptScreen + .getActiveTranscriptRows()), mTopRow + deltaRows)); + invalidate(); + + return true; + } + + public void onSingleTapConfirmed(MotionEvent e) { + } + + public boolean onJumpTapDown(MotionEvent e1, MotionEvent e2) { + // Scroll to bottom + mTopRow = 0; + invalidate(); + return true; + } + + public boolean onJumpTapUp(MotionEvent e1, MotionEvent e2) { + // Scroll to top + mTopRow = -mTranscriptScreen.getActiveTranscriptRows(); + invalidate(); + return true; + } + + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, + float velocityY) { + if (Math.abs(velocityX) > Math.abs(velocityY)) { + // Assume user wanted side to side movement + if (velocityX > 0) { + // Left to right swipe -- previous window + mViewFlipper.showPrevious(); + } else { + // Right to left swipe -- next window + mViewFlipper.showNext(); + } + } else { + // TODO: add animation man's (non animated) fling + mScrollRemainder = 0.0f; + onScroll(e1, e2, 2 * velocityX, -2 * velocityY); + } + return true; + } + + public void onShowPress(MotionEvent e) { + } + + public boolean onDown(MotionEvent e) { + mScrollRemainder = 0.0f; + return true; + } + + // End GestureDetector.OnGestureListener methods + + @Override public boolean onTouchEvent(MotionEvent ev) { + if (mIsSelectingText) { + return onTouchEventWhileSelectingText(ev); + } else { + return mGestureDetector.onTouchEvent(ev); + } + } + + private boolean onTouchEventWhileSelectingText(MotionEvent ev) { + int action = ev.getAction(); + int cx = (int)(ev.getX() / mCharacterWidth); + int cy = Math.max(0, + (int)((ev.getY() + SELECT_TEXT_OFFSET_Y * mScaledDensity) + / mCharacterHeight) + mTopRow); + switch (action) { + case MotionEvent.ACTION_DOWN: + mSelXAnchor = cx; + mSelYAnchor = cy; + mSelX1 = cx; + mSelY1 = cy; + mSelX2 = mSelX1; + mSelY2 = mSelY1; + break; + case MotionEvent.ACTION_MOVE: + case MotionEvent.ACTION_UP: + int minx = Math.min(mSelXAnchor, cx); + int maxx = Math.max(mSelXAnchor, cx); + int miny = Math.min(mSelYAnchor, cy); + int maxy = Math.max(mSelYAnchor, cy); + mSelX1 = minx; + mSelY1 = miny; + mSelX2 = maxx; + mSelY2 = maxy; + if (action == MotionEvent.ACTION_UP) { + ClipboardManager clip = (ClipboardManager) + getContext().getApplicationContext() + .getSystemService(Context.CLIPBOARD_SERVICE); + clip.setText(getSelectedText().trim()); + toggleSelectingText(); + } + invalidate(); + break; + default: + toggleSelectingText(); + invalidate(); + break; + } + return true; + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (LOG_KEY_EVENTS) { + Log.w(TAG, "onKeyDown " + keyCode); + } + if (handleControlKey(keyCode, true)) { + return true; + } else if (handleFnKey(keyCode, true)) { + return true; + } else if (isSystemKey(keyCode, event)) { + // Don't intercept the system keys + return super.onKeyDown(keyCode, event); + } + + // Translate the keyCode into an ASCII character. + + try { + mKeyListener.keyDown(keyCode, event, mTermOut, + getKeypadApplicationMode()); + } catch (IOException e) { + // Ignore I/O exceptions + } + return true; + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + if (LOG_KEY_EVENTS) { + Log.w(TAG, "onKeyUp " + keyCode); + } + if (handleControlKey(keyCode, false)) { + return true; + } else if (handleFnKey(keyCode, false)) { + return true; + } else if (isSystemKey(keyCode, event)) { + // Don't intercept the system keys + return super.onKeyUp(keyCode, event); + } + + mKeyListener.keyUp(keyCode); + return true; + } + + + private boolean handleControlKey(int keyCode, boolean down) { + if (keyCode == mSettings.getControlKeyCode()) { + if (LOG_KEY_EVENTS) { + Log.w(TAG, "handleControlKey " + keyCode); + } + mKeyListener.handleControlKey(down); + return true; + } + return false; + } + + private boolean handleFnKey(int keyCode, boolean down) { + if (keyCode == mSettings.getFnKeyCode()) { + if (LOG_KEY_EVENTS) { + Log.w(TAG, "handleFnKey " + keyCode); + } + mKeyListener.handleFnKey(down); + return true; + } + return false; + } + + private boolean isSystemKey(int keyCode, KeyEvent event) { + return event.isSystem(); + } + + private void updateText() { + if (mTextSize > 0) { + mTextRenderer = new PaintRenderer(mTextSize, mForeground, + mBackground); + } + else { + mTextRenderer = new Bitmap4x8FontRenderer(getResources(), + mForeground, mBackground); + } + mBackgroundPaint.setColor(mBackground); + mCharacterWidth = mTextRenderer.getCharacterWidth(); + mCharacterHeight = mTextRenderer.getCharacterHeight(); + + updateSize(true); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + boolean oldKnownSize = mKnownSize; + if (!mKnownSize) { + mKnownSize = true; + } + updateSize(false); + } + + private void updateSize(int w, int h) { + mColumns = Math.max(1, (int) (((float) w) / mCharacterWidth)); + mRows = Math.max(1, h / mCharacterHeight); + mVisibleColumns = (int) (((float) mVisibleWidth) / mCharacterWidth); + + mTermSession.updateSize(mColumns, mRows); + + // Reset our paging: + mTopRow = 0; + mLeftColumn = 0; + + invalidate(); + } + + public void updateSize(boolean force) { + if (mKnownSize) { + getWindowVisibleDisplayFrame(mVisibleRect); + int w = mVisibleRect.width(); + int h = mVisibleRect.height(); + // Log.w("Term", "(" + w + ", " + h + ")"); + if (force || w != mVisibleWidth || h != mVisibleHeight) { + mVisibleWidth = w; + mVisibleHeight = h; + updateSize(mVisibleWidth, mVisibleHeight); + } + } + } + + @Override + protected void onDraw(Canvas canvas) { + updateSize(false); + int w = getWidth(); + int h = getHeight(); + canvas.drawRect(0, 0, w, h, mBackgroundPaint); + float x = -mLeftColumn * mCharacterWidth; + float y = mCharacterHeight; + int endLine = mTopRow + mRows; + int cx = mEmulator.getCursorCol(); + int cy = mEmulator.getCursorRow(); + for (int i = mTopRow; i < endLine; i++) { + int cursorX = -1; + if (i == cy && mCursorVisible) { + cursorX = cx; + } + int selx1 = -1; + int selx2 = -1; + if ( i >= mSelY1 && i <= mSelY2 ) { + if ( i == mSelY1 ) { + selx1 = mSelX1; + } + if ( i == mSelY2 ) { + selx2 = mSelX2; + } else { + selx2 = mColumns; + } + } + mTranscriptScreen.drawText(i, canvas, x, y, mTextRenderer, cursorX, selx1, selx2, mImeBuffer); + y += mCharacterHeight; + } + } + + private void ensureCursorVisible() { + mTopRow = 0; + if (mVisibleColumns > 0) { + int cx = mEmulator.getCursorCol(); + int visibleCursorX = mEmulator.getCursorCol() - mLeftColumn; + if (visibleCursorX < 0) { + mLeftColumn = cx; + } else if (visibleCursorX >= mVisibleColumns) { + mLeftColumn = (cx - mVisibleColumns) + 1; + } + } + } + + public void toggleSelectingText() { + mIsSelectingText = ! mIsSelectingText; + setVerticalScrollBarEnabled( ! mIsSelectingText ); + if ( ! mIsSelectingText ) { + mSelX1 = -1; + mSelY1 = -1; + mSelX2 = -1; + mSelY2 = -1; + } + } + + public boolean getSelectingText() { + return mIsSelectingText; + } + + public String getSelectedText() { + return mEmulator.getSelectedText(mSelX1, mSelY1, mSelX2, mSelY2); + } +} + +abstract class BaseTextRenderer implements TextRenderer { + protected int[] mForePaint = { + 0xff000000, // Black + 0xffff0000, // Red + 0xff00ff00, // green + 0xffffff00, // yellow + 0xff0000ff, // blue + 0xffff00ff, // magenta + 0xff00ffff, // cyan + 0xffffffff // white -- is overridden by constructor + }; + protected int[] mBackPaint = { + 0xff000000, // Black -- is overridden by constructor + 0xffcc0000, // Red + 0xff00cc00, // green + 0xffcccc00, // yellow + 0xff0000cc, // blue + 0xffff00cc, // magenta + 0xff00cccc, // cyan + 0xffffffff // white + }; + protected final static int mCursorPaint = 0xff808080; + + public BaseTextRenderer(int forePaintColor, int backPaintColor) { + mForePaint[7] = forePaintColor; + mBackPaint[0] = backPaintColor; + + } +} + +class Bitmap4x8FontRenderer extends BaseTextRenderer { + private final static int kCharacterWidth = 4; + private final static int kCharacterHeight = 8; + private Bitmap mFont; + private int mCurrentForeColor; + private int mCurrentBackColor; + private float[] mColorMatrix; + private Paint mPaint; + private static final float BYTE_SCALE = 1.0f / 255.0f; + + public Bitmap4x8FontRenderer(Resources resources, + int forePaintColor, int backPaintColor) { + super(forePaintColor, backPaintColor); + mFont = BitmapFactory.decodeResource(resources, + jackpal.androidterm2.R.drawable.atari_small); + mPaint = new Paint(); + mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); + } + + public float getCharacterWidth() { + return kCharacterWidth; + } + + public int getCharacterHeight() { + return kCharacterHeight; + } + + public void drawTextRun(Canvas canvas, float x, float y, + int lineOffset, char[] text, int index, int count, + boolean cursor, int foreColor, int backColor) { + setColorMatrix(mForePaint[foreColor & 7], + cursor ? mCursorPaint : mBackPaint[backColor & 7]); + int destX = (int) x + kCharacterWidth * lineOffset; + int destY = (int) y; + Rect srcRect = new Rect(); + Rect destRect = new Rect(); + destRect.top = (destY - kCharacterHeight); + destRect.bottom = destY; + for(int i = 0; i < count; i++) { + char c = text[i + index]; + if ((cursor || (c != 32)) && (c < 128)) { + int cellX = c & 31; + int cellY = (c >> 5) & 3; + int srcX = cellX * kCharacterWidth; + int srcY = cellY * kCharacterHeight; + srcRect.set(srcX, srcY, + srcX + kCharacterWidth, srcY + kCharacterHeight); + destRect.left = destX; + destRect.right = destX + kCharacterWidth; + canvas.drawBitmap(mFont, srcRect, destRect, mPaint); + } + destX += kCharacterWidth; + } + } + + private void setColorMatrix(int foreColor, int backColor) { + if ((foreColor != mCurrentForeColor) + || (backColor != mCurrentBackColor) + || (mColorMatrix == null)) { + mCurrentForeColor = foreColor; + mCurrentBackColor = backColor; + if (mColorMatrix == null) { + mColorMatrix = new float[20]; + mColorMatrix[18] = 1.0f; // Just copy Alpha + } + for (int component = 0; component < 3; component++) { + int rightShift = (2 - component) << 3; + int fore = 0xff & (foreColor >> rightShift); + int back = 0xff & (backColor >> rightShift); + int delta = back - fore; + mColorMatrix[component * 6] = delta * BYTE_SCALE; + mColorMatrix[component * 5 + 4] = fore; + } + mPaint.setColorFilter(new ColorMatrixColorFilter(mColorMatrix)); + } + } +} + +class PaintRenderer extends BaseTextRenderer { + public PaintRenderer(int fontSize, int forePaintColor, int backPaintColor) { + super(forePaintColor, backPaintColor); + mTextPaint = new Paint(); + mTextPaint.setTypeface(Typeface.MONOSPACE); + mTextPaint.setAntiAlias(true); + mTextPaint.setTextSize(fontSize); + + mCharHeight = (int) Math.ceil(mTextPaint.getFontSpacing()); + mCharAscent = (int) Math.ceil(mTextPaint.ascent()); + mCharDescent = mCharHeight + mCharAscent; + mCharWidth = mTextPaint.measureText(EXAMPLE_CHAR, 0, 1); + } + + public void drawTextRun(Canvas canvas, float x, float y, int lineOffset, + char[] text, int index, int count, + boolean cursor, int foreColor, int backColor) { + if (cursor) { + mTextPaint.setColor(mCursorPaint); + } else { + mTextPaint.setColor(mBackPaint[backColor & 0x7]); + } + float left = x + lineOffset * mCharWidth; + canvas.drawRect(left, y + mCharAscent, + left + count * mCharWidth, y + mCharDescent, + mTextPaint); + boolean bold = ( foreColor & 0x8 ) != 0; + boolean underline = (backColor & 0x8) != 0; + if (bold) { + mTextPaint.setFakeBoldText(true); + } + if (underline) { + mTextPaint.setUnderlineText(true); + } + mTextPaint.setColor(mForePaint[foreColor & 0x7]); + canvas.drawText(text, index, count, left, y, mTextPaint); + if (bold) { + mTextPaint.setFakeBoldText(false); + } + if (underline) { + mTextPaint.setUnderlineText(false); + } + } + + public int getCharacterHeight() { + return mCharHeight; + } + + public float getCharacterWidth() { + return mCharWidth; + } + + + private Paint mTextPaint; + private float mCharWidth; + private int mCharHeight; + private int mCharAscent; + private int mCharDescent; + private static final char[] EXAMPLE_CHAR = {'X'}; + } + +/** + * An ASCII key listener. Supports control characters and escape. Keeps track of + * the current state of the alt, shift, and control keys. + */ +class TermKeyListener { + /** + * Android key codes that are defined in the Android 2.3 API. + * We want to recognize these codes, because they will be sent to our + * app when we run on Android 2.3 systems. + * But we don't want to accidentally use 2.3-specific APIs. + * So we compile against the Android 1.6 APIs, and have a copy of the codes here. + */ + + /** Key code constant: Unknown key code. */ + public static final int KEYCODE_UNKNOWN = 0; + /** Key code constant: Soft Left key. + * Usually situated below the display on phones and used as a multi-function + * feature key for selecting a software defined function shown on the bottom left + * of the display. */ + public static final int KEYCODE_SOFT_LEFT = 1; + /** Key code constant: Soft Right key. + * Usually situated below the display on phones and used as a multi-function + * feature key for selecting a software defined function shown on the bottom right + * of the display. */ + public static final int KEYCODE_SOFT_RIGHT = 2; + /** Key code constant: Home key. + * This key is handled by the framework and is never delivered to applications. */ + public static final int KEYCODE_HOME = 3; + /** Key code constant: Back key. */ + public static final int KEYCODE_BACK = 4; + /** Key code constant: Call key. */ + public static final int KEYCODE_CALL = 5; + /** Key code constant: End Call key. */ + public static final int KEYCODE_ENDCALL = 6; + /** Key code constant: '0' key. */ + public static final int KEYCODE_0 = 7; + /** Key code constant: '1' key. */ + public static final int KEYCODE_1 = 8; + /** Key code constant: '2' key. */ + public static final int KEYCODE_2 = 9; + /** Key code constant: '3' key. */ + public static final int KEYCODE_3 = 10; + /** Key code constant: '4' key. */ + public static final int KEYCODE_4 = 11; + /** Key code constant: '5' key. */ + public static final int KEYCODE_5 = 12; + /** Key code constant: '6' key. */ + public static final int KEYCODE_6 = 13; + /** Key code constant: '7' key. */ + public static final int KEYCODE_7 = 14; + /** Key code constant: '8' key. */ + public static final int KEYCODE_8 = 15; + /** Key code constant: '9' key. */ + public static final int KEYCODE_9 = 16; + /** Key code constant: '*' key. */ + public static final int KEYCODE_STAR = 17; + /** Key code constant: '#' key. */ + public static final int KEYCODE_POUND = 18; + /** Key code constant: Directional Pad Up key. + * May also be synthesized from trackball motions. */ + public static final int KEYCODE_DPAD_UP = 19; + /** Key code constant: Directional Pad Down key. + * May also be synthesized from trackball motions. */ + public static final int KEYCODE_DPAD_DOWN = 20; + /** Key code constant: Directional Pad Left key. + * May also be synthesized from trackball motions. */ + public static final int KEYCODE_DPAD_LEFT = 21; + /** Key code constant: Directional Pad Right key. + * May also be synthesized from trackball motions. */ + public static final int KEYCODE_DPAD_RIGHT = 22; + /** Key code constant: Directional Pad Center key. + * May also be synthesized from trackball motions. */ + public static final int KEYCODE_DPAD_CENTER = 23; + /** Key code constant: Volume Up key. + * Adjusts the speaker volume up. */ + public static final int KEYCODE_VOLUME_UP = 24; + /** Key code constant: Volume Down key. + * Adjusts the speaker volume down. */ + public static final int KEYCODE_VOLUME_DOWN = 25; + /** Key code constant: Power key. */ + public static final int KEYCODE_POWER = 26; + /** Key code constant: Camera key. + * Used to launch a camera application or take pictures. */ + public static final int KEYCODE_CAMERA = 27; + /** Key code constant: Clear key. */ + public static final int KEYCODE_CLEAR = 28; + /** Key code constant: 'A' key. */ + public static final int KEYCODE_A = 29; + /** Key code constant: 'B' key. */ + public static final int KEYCODE_B = 30; + /** Key code constant: 'C' key. */ + public static final int KEYCODE_C = 31; + /** Key code constant: 'D' key. */ + public static final int KEYCODE_D = 32; + /** Key code constant: 'E' key. */ + public static final int KEYCODE_E = 33; + /** Key code constant: 'F' key. */ + public static final int KEYCODE_F = 34; + /** Key code constant: 'G' key. */ + public static final int KEYCODE_G = 35; + /** Key code constant: 'H' key. */ + public static final int KEYCODE_H = 36; + /** Key code constant: 'I' key. */ + public static final int KEYCODE_I = 37; + /** Key code constant: 'J' key. */ + public static final int KEYCODE_J = 38; + /** Key code constant: 'K' key. */ + public static final int KEYCODE_K = 39; + /** Key code constant: 'L' key. */ + public static final int KEYCODE_L = 40; + /** Key code constant: 'M' key. */ + public static final int KEYCODE_M = 41; + /** Key code constant: 'N' key. */ + public static final int KEYCODE_N = 42; + /** Key code constant: 'O' key. */ + public static final int KEYCODE_O = 43; + /** Key code constant: 'P' key. */ + public static final int KEYCODE_P = 44; + /** Key code constant: 'Q' key. */ + public static final int KEYCODE_Q = 45; + /** Key code constant: 'R' key. */ + public static final int KEYCODE_R = 46; + /** Key code constant: 'S' key. */ + public static final int KEYCODE_S = 47; + /** Key code constant: 'T' key. */ + public static final int KEYCODE_T = 48; + /** Key code constant: 'U' key. */ + public static final int KEYCODE_U = 49; + /** Key code constant: 'V' key. */ + public static final int KEYCODE_V = 50; + /** Key code constant: 'W' key. */ + public static final int KEYCODE_W = 51; + /** Key code constant: 'X' key. */ + public static final int KEYCODE_X = 52; + /** Key code constant: 'Y' key. */ + public static final int KEYCODE_Y = 53; + /** Key code constant: 'Z' key. */ + public static final int KEYCODE_Z = 54; + /** Key code constant: ',' key. */ + public static final int KEYCODE_COMMA = 55; + /** Key code constant: '.' key. */ + public static final int KEYCODE_PERIOD = 56; + /** Key code constant: Left Alt modifier key. */ + public static final int KEYCODE_ALT_LEFT = 57; + /** Key code constant: Right Alt modifier key. */ + public static final int KEYCODE_ALT_RIGHT = 58; + /** Key code constant: Left Shift modifier key. */ + public static final int KEYCODE_SHIFT_LEFT = 59; + /** Key code constant: Right Shift modifier key. */ + public static final int KEYCODE_SHIFT_RIGHT = 60; + /** Key code constant: Tab key. */ + public static final int KEYCODE_TAB = 61; + /** Key code constant: Space key. */ + public static final int KEYCODE_SPACE = 62; + /** Key code constant: Symbol modifier key. + * Used to enter alternate symbols. */ + public static final int KEYCODE_SYM = 63; + /** Key code constant: Explorer special function key. + * Used to launch a browser application. */ + public static final int KEYCODE_EXPLORER = 64; + /** Key code constant: Envelope special function key. + * Used to launch a mail application. */ + public static final int KEYCODE_ENVELOPE = 65; + /** Key code constant: Enter key. */ + public static final int KEYCODE_ENTER = 66; + /** Key code constant: Backspace key. + * Deletes characters before the insertion point, unlike {@link #KEYCODE_FORWARD_DEL}. */ + public static final int KEYCODE_DEL = 67; + /** Key code constant: '`' (backtick) key. */ + public static final int KEYCODE_GRAVE = 68; + /** Key code constant: '-'. */ + public static final int KEYCODE_MINUS = 69; + /** Key code constant: '=' key. */ + public static final int KEYCODE_EQUALS = 70; + /** Key code constant: '[' key. */ + public static final int KEYCODE_LEFT_BRACKET = 71; + /** Key code constant: ']' key. */ + public static final int KEYCODE_RIGHT_BRACKET = 72; + /** Key code constant: '\' key. */ + public static final int KEYCODE_BACKSLASH = 73; + /** Key code constant: ';' key. */ + public static final int KEYCODE_SEMICOLON = 74; + /** Key code constant: ''' (apostrophe) key. */ + public static final int KEYCODE_APOSTROPHE = 75; + /** Key code constant: '/' key. */ + public static final int KEYCODE_SLASH = 76; + /** Key code constant: '@' key. */ + public static final int KEYCODE_AT = 77; + /** Key code constant: Number modifier key. + * Used to enter numeric symbols. + * This key is not Num Lock; it is more like {@link #KEYCODE_ALT_LEFT} and is + * interpreted as an ALT key by {@link android.text.method.MetaKeyKeyListener}. */ + public static final int KEYCODE_NUM = 78; + /** Key code constant: Headset Hook key. + * Used to hang up calls and stop media. */ + public static final int KEYCODE_HEADSETHOOK = 79; + /** Key code constant: Camera Focus key. + * Used to focus the camera. */ + public static final int KEYCODE_FOCUS = 80; // *Camera* focus + /** Key code constant: '+' key. */ + public static final int KEYCODE_PLUS = 81; + /** Key code constant: Menu key. */ + public static final int KEYCODE_MENU = 82; + /** Key code constant: Notification key. */ + public static final int KEYCODE_NOTIFICATION = 83; + /** Key code constant: Search key. */ + public static final int KEYCODE_SEARCH = 84; + /** Key code constant: Play/Pause media key. */ + public static final int KEYCODE_MEDIA_PLAY_PAUSE= 85; + /** Key code constant: Stop media key. */ + public static final int KEYCODE_MEDIA_STOP = 86; + /** Key code constant: Play Next media key. */ + public static final int KEYCODE_MEDIA_NEXT = 87; + /** Key code constant: Play Previous media key. */ + public static final int KEYCODE_MEDIA_PREVIOUS = 88; + /** Key code constant: Rewind media key. */ + public static final int KEYCODE_MEDIA_REWIND = 89; + /** Key code constant: Fast Forward media key. */ + public static final int KEYCODE_MEDIA_FAST_FORWARD = 90; + /** Key code constant: Mute key. + * Mutes the microphone, unlike {@link #KEYCODE_VOLUME_MUTE}. */ + public static final int KEYCODE_MUTE = 91; + /** Key code constant: Page Up key. */ + public static final int KEYCODE_PAGE_UP = 92; + /** Key code constant: Page Down key. */ + public static final int KEYCODE_PAGE_DOWN = 93; + /** Key code constant: Picture Symbols modifier key. + * Used to switch symbol sets (Emoji, Kao-moji). */ + public static final int KEYCODE_PICTSYMBOLS = 94; // switch symbol-sets (Emoji,Kao-moji) + /** Key code constant: Switch Charset modifier key. + * Used to switch character sets (Kanji, Katakana). */ + public static final int KEYCODE_SWITCH_CHARSET = 95; // switch char-sets (Kanji,Katakana) + /** Key code constant: A Button key. + * On a game controller, the A button should be either the button labeled A + * or the first button on the upper row of controller buttons. */ + public static final int KEYCODE_BUTTON_A = 96; + /** Key code constant: B Button key. + * On a game controller, the B button should be either the button labeled B + * or the second button on the upper row of controller buttons. */ + public static final int KEYCODE_BUTTON_B = 97; + /** Key code constant: C Button key. + * On a game controller, the C button should be either the button labeled C + * or the third button on the upper row of controller buttons. */ + public static final int KEYCODE_BUTTON_C = 98; + /** Key code constant: X Button key. + * On a game controller, the X button should be either the button labeled X + * or the first button on the lower row of controller buttons. */ + public static final int KEYCODE_BUTTON_X = 99; + /** Key code constant: Y Button key. + * On a game controller, the Y button should be either the button labeled Y + * or the second button on the lower row of controller buttons. */ + public static final int KEYCODE_BUTTON_Y = 100; + /** Key code constant: Z Button key. + * On a game controller, the Z button should be either the button labeled Z + * or the third button on the lower row of controller buttons. */ + public static final int KEYCODE_BUTTON_Z = 101; + /** Key code constant: L1 Button key. + * On a game controller, the L1 button should be either the button labeled L1 (or L) + * or the top left trigger button. */ + public static final int KEYCODE_BUTTON_L1 = 102; + /** Key code constant: R1 Button key. + * On a game controller, the R1 button should be either the button labeled R1 (or R) + * or the top right trigger button. */ + public static final int KEYCODE_BUTTON_R1 = 103; + /** Key code constant: L2 Button key. + * On a game controller, the L2 button should be either the button labeled L2 + * or the bottom left trigger button. */ + public static final int KEYCODE_BUTTON_L2 = 104; + /** Key code constant: R2 Button key. + * On a game controller, the R2 button should be either the button labeled R2 + * or the bottom right trigger button. */ + public static final int KEYCODE_BUTTON_R2 = 105; + /** Key code constant: Left Thumb Button key. + * On a game controller, the left thumb button indicates that the left (or only) + * joystick is pressed. */ + public static final int KEYCODE_BUTTON_THUMBL = 106; + /** Key code constant: Right Thumb Button key. + * On a game controller, the right thumb button indicates that the right + * joystick is pressed. */ + public static final int KEYCODE_BUTTON_THUMBR = 107; + /** Key code constant: Start Button key. + * On a game controller, the button labeled Start. */ + public static final int KEYCODE_BUTTON_START = 108; + /** Key code constant: Select Button key. + * On a game controller, the button labeled Select. */ + public static final int KEYCODE_BUTTON_SELECT = 109; + /** Key code constant: Mode Button key. + * On a game controller, the button labeled Mode. */ + public static final int KEYCODE_BUTTON_MODE = 110; + /** Key code constant: Escape key. */ + public static final int KEYCODE_ESCAPE = 111; + /** Key code constant: Forward Delete key. + * Deletes characters ahead of the insertion point, unlike {@link #KEYCODE_DEL}. */ + public static final int KEYCODE_FORWARD_DEL = 112; + /** Key code constant: Left Control modifier key. */ + public static final int KEYCODE_CTRL_LEFT = 113; + /** Key code constant: Right Control modifier key. */ + public static final int KEYCODE_CTRL_RIGHT = 114; + /** Key code constant: Caps Lock modifier key. */ + public static final int KEYCODE_CAPS_LOCK = 115; + /** Key code constant: Scroll Lock key. */ + public static final int KEYCODE_SCROLL_LOCK = 116; + /** Key code constant: Left Meta modifier key. */ + public static final int KEYCODE_META_LEFT = 117; + /** Key code constant: Right Meta modifier key. */ + public static final int KEYCODE_META_RIGHT = 118; + /** Key code constant: Function modifier key. */ + public static final int KEYCODE_FUNCTION = 119; + /** Key code constant: System Request / Print Screen key. */ + public static final int KEYCODE_SYSRQ = 120; + /** Key code constant: Break / Pause key. */ + public static final int KEYCODE_BREAK = 121; + /** Key code constant: Home Movement key. + * Used for scrolling or moving the cursor around to the start of a line + * or to the top of a list. */ + public static final int KEYCODE_MOVE_HOME = 122; + /** Key code constant: End Movement key. + * Used for scrolling or moving the cursor around to the end of a line + * or to the bottom of a list. */ + public static final int KEYCODE_MOVE_END = 123; + /** Key code constant: Insert key. + * Toggles insert / overwrite edit mode. */ + public static final int KEYCODE_INSERT = 124; + /** Key code constant: Forward key. + * Navigates forward in the history stack. Complement of {@link #KEYCODE_BACK}. */ + public static final int KEYCODE_FORWARD = 125; + /** Key code constant: Play media key. */ + public static final int KEYCODE_MEDIA_PLAY = 126; + /** Key code constant: Pause media key. */ + public static final int KEYCODE_MEDIA_PAUSE = 127; + /** Key code constant: Close media key. + * May be used to close a CD tray, for example. */ + public static final int KEYCODE_MEDIA_CLOSE = 128; + /** Key code constant: Eject media key. + * May be used to eject a CD tray, for example. */ + public static final int KEYCODE_MEDIA_EJECT = 129; + /** Key code constant: Record media key. */ + public static final int KEYCODE_MEDIA_RECORD = 130; + /** Key code constant: F1 key. */ + public static final int KEYCODE_F1 = 131; + /** Key code constant: F2 key. */ + public static final int KEYCODE_F2 = 132; + /** Key code constant: F3 key. */ + public static final int KEYCODE_F3 = 133; + /** Key code constant: F4 key. */ + public static final int KEYCODE_F4 = 134; + /** Key code constant: F5 key. */ + public static final int KEYCODE_F5 = 135; + /** Key code constant: F6 key. */ + public static final int KEYCODE_F6 = 136; + /** Key code constant: F7 key. */ + public static final int KEYCODE_F7 = 137; + /** Key code constant: F8 key. */ + public static final int KEYCODE_F8 = 138; + /** Key code constant: F9 key. */ + public static final int KEYCODE_F9 = 139; + /** Key code constant: F10 key. */ + public static final int KEYCODE_F10 = 140; + /** Key code constant: F11 key. */ + public static final int KEYCODE_F11 = 141; + /** Key code constant: F12 key. */ + public static final int KEYCODE_F12 = 142; + /** Key code constant: Num Lock modifier key. + * This is the Num Lock key; it is different from {@link #KEYCODE_NUM}. + * This key generally modifies the behavior of other keys on the numeric keypad. */ + public static final int KEYCODE_NUM_LOCK = 143; + /** Key code constant: Numeric keypad '0' key. */ + public static final int KEYCODE_NUMPAD_0 = 144; + /** Key code constant: Numeric keypad '1' key. */ + public static final int KEYCODE_NUMPAD_1 = 145; + /** Key code constant: Numeric keypad '2' key. */ + public static final int KEYCODE_NUMPAD_2 = 146; + /** Key code constant: Numeric keypad '3' key. */ + public static final int KEYCODE_NUMPAD_3 = 147; + /** Key code constant: Numeric keypad '4' key. */ + public static final int KEYCODE_NUMPAD_4 = 148; + /** Key code constant: Numeric keypad '5' key. */ + public static final int KEYCODE_NUMPAD_5 = 149; + /** Key code constant: Numeric keypad '6' key. */ + public static final int KEYCODE_NUMPAD_6 = 150; + /** Key code constant: Numeric keypad '7' key. */ + public static final int KEYCODE_NUMPAD_7 = 151; + /** Key code constant: Numeric keypad '8' key. */ + public static final int KEYCODE_NUMPAD_8 = 152; + /** Key code constant: Numeric keypad '9' key. */ + public static final int KEYCODE_NUMPAD_9 = 153; + /** Key code constant: Numeric keypad '/' key (for division). */ + public static final int KEYCODE_NUMPAD_DIVIDE = 154; + /** Key code constant: Numeric keypad '*' key (for multiplication). */ + public static final int KEYCODE_NUMPAD_MULTIPLY = 155; + /** Key code constant: Numeric keypad '-' key (for subtraction). */ + public static final int KEYCODE_NUMPAD_SUBTRACT = 156; + /** Key code constant: Numeric keypad '+' key (for addition). */ + public static final int KEYCODE_NUMPAD_ADD = 157; + /** Key code constant: Numeric keypad '.' key (for decimals or digit grouping). */ + public static final int KEYCODE_NUMPAD_DOT = 158; + /** Key code constant: Numeric keypad ',' key (for decimals or digit grouping). */ + public static final int KEYCODE_NUMPAD_COMMA = 159; + /** Key code constant: Numeric keypad Enter key. */ + public static final int KEYCODE_NUMPAD_ENTER = 160; + /** Key code constant: Numeric keypad '=' key. */ + public static final int KEYCODE_NUMPAD_EQUALS = 161; + /** Key code constant: Numeric keypad '(' key. */ + public static final int KEYCODE_NUMPAD_LEFT_PAREN = 162; + /** Key code constant: Numeric keypad ')' key. */ + public static final int KEYCODE_NUMPAD_RIGHT_PAREN = 163; + /** Key code constant: Volume Mute key. + * Mutes the speaker, unlike {@link #KEYCODE_MUTE}. + * This key should normally be implemented as a toggle such that the first press + * mutes the speaker and the second press restores the original volume. */ + public static final int KEYCODE_VOLUME_MUTE = 164; + /** Key code constant: Info key. + * Common on TV remotes to show additional information related to what is + * currently being viewed. */ + public static final int KEYCODE_INFO = 165; + /** Key code constant: Channel up key. + * On TV remotes, increments the television channel. */ + public static final int KEYCODE_CHANNEL_UP = 166; + /** Key code constant: Channel down key. + * On TV remotes, decrements the television channel. */ + public static final int KEYCODE_CHANNEL_DOWN = 167; + /** Key code constant: Zoom in key. */ + public static final int KEYCODE_ZOOM_IN = 168; + /** Key code constant: Zoom out key. */ + public static final int KEYCODE_ZOOM_OUT = 169; + /** Key code constant: TV key. + * On TV remotes, switches to viewing live TV. */ + public static final int KEYCODE_TV = 170; + /** Key code constant: Window key. + * On TV remotes, toggles picture-in-picture mode or other windowing functions. */ + public static final int KEYCODE_WINDOW = 171; + /** Key code constant: Guide key. + * On TV remotes, shows a programming guide. */ + public static final int KEYCODE_GUIDE = 172; + /** Key code constant: DVR key. + * On some TV remotes, switches to a DVR mode for recorded shows. */ + public static final int KEYCODE_DVR = 173; + /** Key code constant: Bookmark key. + * On some TV remotes, bookmarks content or web pages. */ + public static final int KEYCODE_BOOKMARK = 174; + /** Key code constant: Toggle captions key. + * Switches the mode for closed-captioning text, for example during television shows. */ + public static final int KEYCODE_CAPTIONS = 175; + /** Key code constant: Settings key. + * Starts the system settings activity. */ + public static final int KEYCODE_SETTINGS = 176; + /** Key code constant: TV power key. + * On TV remotes, toggles the power on a television screen. */ + public static final int KEYCODE_TV_POWER = 177; + /** Key code constant: TV input key. + * On TV remotes, switches the input on a television screen. */ + public static final int KEYCODE_TV_INPUT = 178; + /** Key code constant: Set-top-box power key. + * On TV remotes, toggles the power on an external Set-top-box. */ + public static final int KEYCODE_STB_POWER = 179; + /** Key code constant: Set-top-box input key. + * On TV remotes, switches the input mode on an external Set-top-box. */ + public static final int KEYCODE_STB_INPUT = 180; + /** Key code constant: A/V Receiver power key. + * On TV remotes, toggles the power on an external A/V Receiver. */ + public static final int KEYCODE_AVR_POWER = 181; + /** Key code constant: A/V Receiver input key. + * On TV remotes, switches the input mode on an external A/V Receiver. */ + public static final int KEYCODE_AVR_INPUT = 182; + /** Key code constant: Red "programmable" key. + * On TV remotes, acts as a contextual/programmable key. */ + public static final int KEYCODE_PROG_RED = 183; + /** Key code constant: Green "programmable" key. + * On TV remotes, actsas a contextual/programmable key. */ + public static final int KEYCODE_PROG_GREEN = 184; + /** Key code constant: Yellow "programmable" key. + * On TV remotes, acts as a contextual/programmable key. */ + public static final int KEYCODE_PROG_YELLOW = 185; + /** Key code constant: Blue "programmable" key. + * On TV remotes, acts as a contextual/programmable key. */ + public static final int KEYCODE_PROG_BLUE = 186; + + private static final int LAST_KEYCODE = KEYCODE_PROG_BLUE; + + private String[] mKeyCodes = new String[256]; + private String[] mAppKeyCodes = new String[256]; + + private void initKeyCodes() { + mKeyCodes[KEYCODE_DPAD_CENTER] = "\015"; + mKeyCodes[KEYCODE_DPAD_UP] = "\033[A"; + mKeyCodes[KEYCODE_DPAD_DOWN] = "\033[B"; + mKeyCodes[KEYCODE_DPAD_RIGHT] = "\033[C"; + mKeyCodes[KEYCODE_DPAD_LEFT] = "\033[D"; + mKeyCodes[KEYCODE_F1] = "\033[OP"; + mKeyCodes[KEYCODE_F2] = "\033[OQ"; + mKeyCodes[KEYCODE_F3] = "\033[OR"; + mKeyCodes[KEYCODE_F4] = "\033[OS"; + mKeyCodes[KEYCODE_F5] = "\033[15~"; + mKeyCodes[KEYCODE_F6] = "\033[17~"; + mKeyCodes[KEYCODE_F7] = "\033[18~"; + mKeyCodes[KEYCODE_F8] = "\033[19~"; + mKeyCodes[KEYCODE_F9] = "\033[20~"; + mKeyCodes[KEYCODE_F10] = "\033[21~"; + mKeyCodes[KEYCODE_F11] = "\033[23~"; + mKeyCodes[KEYCODE_F12] = "\033[24~"; + mKeyCodes[KEYCODE_SYSRQ] = "\033[32~"; // Sys Request / Print + // Is this Scroll lock? mKeyCodes[Cancel] = "\033[33~"; + mKeyCodes[KEYCODE_BREAK] = "\033[34~"; // Pause/Break + + mKeyCodes[KEYCODE_TAB] = "\011"; + mKeyCodes[KEYCODE_ENTER] = "\015"; + mKeyCodes[KEYCODE_ESCAPE] = "\033"; + + mKeyCodes[KEYCODE_INSERT] = "\033[2~"; + mKeyCodes[KEYCODE_FORWARD_DEL] = "\033[3~"; + mKeyCodes[KEYCODE_MOVE_HOME] = "\033[1~"; + mKeyCodes[KEYCODE_MOVE_END] = "\033[4~"; + mKeyCodes[KEYCODE_PAGE_UP] = "\033[5~"; + mKeyCodes[KEYCODE_PAGE_DOWN] = "\033[6~"; + mKeyCodes[KEYCODE_DEL]= "\177"; + mKeyCodes[KEYCODE_NUM_LOCK] = "\033OP"; + mKeyCodes[KEYCODE_NUMPAD_DIVIDE] = "/"; + mKeyCodes[KEYCODE_NUMPAD_MULTIPLY] = "*"; + mKeyCodes[KEYCODE_NUMPAD_SUBTRACT] = "-"; + mKeyCodes[KEYCODE_NUMPAD_ADD] = "+"; + mKeyCodes[KEYCODE_NUMPAD_ENTER] = "\015"; + mKeyCodes[KEYCODE_NUMPAD_EQUALS] = "="; + mKeyCodes[KEYCODE_NUMPAD_DOT] = "."; + mKeyCodes[KEYCODE_NUMPAD_COMMA] = ","; + mKeyCodes[KEYCODE_NUMPAD_0] = "0"; + mKeyCodes[KEYCODE_NUMPAD_1] = "1"; + mKeyCodes[KEYCODE_NUMPAD_2] = "2"; + mKeyCodes[KEYCODE_NUMPAD_3] = "3"; + mKeyCodes[KEYCODE_NUMPAD_4] = "4"; + mKeyCodes[KEYCODE_NUMPAD_5] = "5"; + mKeyCodes[KEYCODE_NUMPAD_6] = "6"; + mKeyCodes[KEYCODE_NUMPAD_7] = "7"; + mKeyCodes[KEYCODE_NUMPAD_8] = "8"; + mKeyCodes[KEYCODE_NUMPAD_9] = "9"; + + mAppKeyCodes[KEYCODE_DPAD_UP] = "\033OA"; + mAppKeyCodes[KEYCODE_DPAD_DOWN] = "\033OB"; + mAppKeyCodes[KEYCODE_DPAD_RIGHT] = "\033OC"; + mAppKeyCodes[KEYCODE_DPAD_LEFT] = "\033OD"; + mAppKeyCodes[KEYCODE_NUMPAD_DIVIDE] = "\033Oo"; + mAppKeyCodes[KEYCODE_NUMPAD_MULTIPLY] = "\033Oj"; + mAppKeyCodes[KEYCODE_NUMPAD_SUBTRACT] = "\033Om"; + mAppKeyCodes[KEYCODE_NUMPAD_ADD] = "\033Ok"; + mAppKeyCodes[KEYCODE_NUMPAD_ENTER] = "\033OM"; + mAppKeyCodes[KEYCODE_NUMPAD_EQUALS] = "\033OX"; + mAppKeyCodes[KEYCODE_NUMPAD_DOT] = "\033On"; + mAppKeyCodes[KEYCODE_NUMPAD_COMMA] = "\033Ol"; + mAppKeyCodes[KEYCODE_NUMPAD_0] = "\033Op"; + mAppKeyCodes[KEYCODE_NUMPAD_1] = "\033Oq"; + mAppKeyCodes[KEYCODE_NUMPAD_2] = "\033Or"; + mAppKeyCodes[KEYCODE_NUMPAD_3] = "\033Os"; + mAppKeyCodes[KEYCODE_NUMPAD_4] = "\033Ot"; + mAppKeyCodes[KEYCODE_NUMPAD_5] = "\033Ou"; + mAppKeyCodes[KEYCODE_NUMPAD_6] = "\033Ov"; + mAppKeyCodes[KEYCODE_NUMPAD_7] = "\033Ow"; + mAppKeyCodes[KEYCODE_NUMPAD_8] = "\033Ox"; + mAppKeyCodes[KEYCODE_NUMPAD_9] = "\033Oy"; + } + + /** + * The state engine for a modifier key. Can be pressed, released, locked, + * and so on. + * + */ + private class ModifierKey { + + private int mState; + + private static final int UNPRESSED = 0; + + private static final int PRESSED = 1; + + private static final int RELEASED = 2; + + private static final int USED = 3; + + private static final int LOCKED = 4; + + /** + * Construct a modifier key. UNPRESSED by default. + * + */ + public ModifierKey() { + mState = UNPRESSED; + } + + public void onPress() { + switch (mState) { + case PRESSED: + // This is a repeat before use + break; + case RELEASED: + mState = LOCKED; + break; + case USED: + // This is a repeat after use + break; + case LOCKED: + mState = UNPRESSED; + break; + default: + mState = PRESSED; + break; + } + } + + public void onRelease() { + switch (mState) { + case USED: + mState = UNPRESSED; + break; + case PRESSED: + mState = RELEASED; + break; + default: + // Leave state alone + break; + } + } + + public void adjustAfterKeypress() { + switch (mState) { + case PRESSED: + mState = USED; + break; + case RELEASED: + mState = UNPRESSED; + break; + default: + // Leave state alone + break; + } + } + + public boolean isActive() { + return mState != UNPRESSED; + } + } + + private ModifierKey mAltKey = new ModifierKey(); + + private ModifierKey mCapKey = new ModifierKey(); + + private ModifierKey mControlKey = new ModifierKey(); + + private ModifierKey mFnKey = new ModifierKey(); + + private boolean mCapsLock; + + static public final int KEYCODE_OFFSET = 1000; + + /** + * Construct a term key listener. + * + */ + public TermKeyListener() { + initKeyCodes(); + } + + public void handleControlKey(boolean down) { + if (down) { + mControlKey.onPress(); + } else { + mControlKey.onRelease(); + } + } + + public void handleFnKey(boolean down) { + if (down) { + mFnKey.onPress(); + } else { + mFnKey.onRelease(); + } + } + + public int mapControlChar(int ch) { + int result = ch; + if (mControlKey.isActive()) { + // Search is the control key. + if (result >= 'a' && result <= 'z') { + result = (char) (result - 'a' + '\001'); + } else if (result >= 'A' && result <= 'Z') { + result = (char) (result - 'A' + '\001'); + } else if (result == ' ' || result == '2') { + result = 0; + } else if (result == '[' || result == '3') { + result = 27; // ^[ (Esc) + } else if (result == '\\' || result == '4') { + result = 28; + } else if (result == ']' || result == '5') { + result = 29; + } else if (result == '^' || result == '6') { + result = 30; // control-^ + } else if (result == '_' || result == '7') { + result = 31; + } else if (result == '8') { + result = 127; // DEL + } else if (result == '9') { + result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_F11; + } else if (result == '0') { + result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_F12; + } + } else if (mFnKey.isActive()) { + if (result == 'w' || result == 'W') { + result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_UP; + } else if (result == 'a' || result == 'A') { + result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_LEFT; + } else if (result == 's' || result == 'S') { + result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_DOWN; + } else if (result == 'd' || result == 'D') { + result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_RIGHT; + } else if (result == 'p' || result == 'P') { + result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_PAGE_UP; + } else if (result == 'n' || result == 'N') { + result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_PAGE_DOWN; + } else if (result == 't' || result == 'T') { + result = KEYCODE_OFFSET + KeyEvent.KEYCODE_TAB; + } else if (result == 'l' || result == 'L') { + result = '|'; + } else if (result == 'u' || result == 'U') { + result = '_'; + } else if (result == 'e' || result == 'E') { + result = 27; // ^[ (Esc) + } else if (result == '.') { + result = 28; // ^\ + } else if (result > '0' && result <= '9') { + // F1-F9 + result = (char)(result + KEYCODE_OFFSET + TermKeyListener.KEYCODE_F1 - 1); + } else if (result == '0') { + result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_F10; + } else if (result == 'i' || result == 'I') { + result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_INSERT; + } else if (result == 'x' || result == 'X') { + result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_FORWARD_DEL; + } else if (result == 'h' || result == 'H') { + result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_MOVE_HOME; + } else if (result == 'f' || result == 'F') { + result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_MOVE_END; + } + } + + if (result > -1) { + mAltKey.adjustAfterKeypress(); + mCapKey.adjustAfterKeypress(); + mControlKey.adjustAfterKeypress(); + mFnKey.adjustAfterKeypress(); + } + + return result; + } + + /** + * Handle a keyDown event. + * + * @param keyCode the keycode of the keyDown event + * + */ + public void keyDown(int keyCode, KeyEvent event, OutputStream out, boolean appMode) throws IOException { + if (handleKeyCode(keyCode, out, appMode)) { + return; + } + int result = -1; + switch (keyCode) { + case KeyEvent.KEYCODE_ALT_RIGHT: + case KeyEvent.KEYCODE_ALT_LEFT: + mAltKey.onPress(); + break; + + case KeyEvent.KEYCODE_SHIFT_LEFT: + case KeyEvent.KEYCODE_SHIFT_RIGHT: + mCapKey.onPress(); + break; + + case KEYCODE_CTRL_LEFT: + case KEYCODE_CTRL_RIGHT: + mControlKey.onPress(); + break; + + case KEYCODE_CAPS_LOCK: + if (event.getRepeatCount() == 0) { + mCapsLock = !mCapsLock; + } + break; + + default: { + result = event.getUnicodeChar( + (mCapKey.isActive() || mCapsLock ? KeyEvent.META_SHIFT_ON : 0) | + (mAltKey.isActive() ? KeyEvent.META_ALT_ON : 0)); + break; + } + } + + result = mapControlChar(result); + + if (result >= KEYCODE_OFFSET) { + handleKeyCode(result - KEYCODE_OFFSET, out, appMode); + } else if (result >= 0) { + out.write(result); + } + } + + public boolean handleKeyCode(int keyCode, OutputStream out, boolean appMode) throws IOException { + if (keyCode >= 0 && keyCode < mKeyCodes.length) { + String code = null; + if (appMode) { + code = mAppKeyCodes[keyCode]; + } + if (code == null) { + code = mKeyCodes[keyCode]; + } + if (code != null) { + int length = code.length(); + for (int i = 0; i < length; i++) { + out.write(code.charAt(i)); + } + return true; + } + } + return false; + } + + /** + * Handle a keyUp event. + * + * @param keyCode the keyCode of the keyUp event + */ + public void keyUp(int keyCode) { + switch (keyCode) { + case KeyEvent.KEYCODE_ALT_LEFT: + case KeyEvent.KEYCODE_ALT_RIGHT: + mAltKey.onRelease(); + break; + case KeyEvent.KEYCODE_SHIFT_LEFT: + case KeyEvent.KEYCODE_SHIFT_RIGHT: + mCapKey.onRelease(); + break; + + case KEYCODE_CTRL_LEFT: + case KEYCODE_CTRL_RIGHT: + mControlKey.onRelease(); + break; + + default: + // Ignore other keyUps + break; + } + } +} diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index 31109cc1b..30833024f 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -16,66 +16,42 @@ package jackpal.androidterm2; -import java.io.FileDescriptor; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import android.app.Activity; import android.app.AlertDialog; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; +import android.content.ServiceConnection; import android.content.res.Configuration; import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Canvas; -import android.graphics.ColorMatrixColorFilter; -import android.graphics.Paint; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffXfermode; -import android.graphics.Rect; -import android.graphics.Typeface; import android.net.Uri; +import android.net.wifi.WifiManager; import android.os.Bundle; -import android.os.Handler; -import android.os.Message; +import android.os.IBinder; +import android.os.PowerManager; import android.preference.PreferenceManager; import android.text.ClipboardManager; -import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; -import android.view.GestureDetector; -import android.view.KeyEvent; +import android.view.Gravity; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.Window; import android.view.WindowManager; -import android.view.inputmethod.BaseInputConnection; -import android.view.inputmethod.CompletionInfo; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.ExtractedText; -import android.view.inputmethod.ExtractedTextRequest; -import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; -import android.widget.TextView; - -import android.content.ComponentName; -import android.content.Intent; -import android.content.ServiceConnection; -import android.os.Binder; -import android.os.IBinder; +import android.widget.FrameLayout; +import android.widget.Toast; -import android.net.wifi.WifiManager; -import android.os.PowerManager; +import jackpal.androidterm2.session.TermSession; +import jackpal.androidterm2.util.TermSettings; /** * A terminal emulator activity. @@ -83,132 +59,19 @@ public class Term extends Activity { /** - * Set to true to add debugging code and logging. - */ - public static final boolean DEBUG = false; - - /** - * Set to true to log IME calls. - */ - public static final boolean LOG_IME = DEBUG && false; - - /** - * Set to true to log each character received from the remote process to the - * android log, which makes it easier to debug some kinds of problems with - * emulating escape sequences and control codes. - */ - public static final boolean LOG_CHARACTERS_FLAG = DEBUG && false; - - /** - * Set to true to log unknown escape sequences. - */ - public static final boolean LOG_UNKNOWN_ESCAPE_SEQUENCES = DEBUG && false; - - /** - * The tag we use when logging, so that our messages can be distinguished - * from other messages in the log. Public because it's used by several - * classes. - */ - public static final String LOG_TAG = "Term"; - - /** - * Our main view. Displays the emulated terminal screen. - */ - private EmulatorView mEmulatorView; - - /** - * The pseudo-teletype (pty) file descriptor that we use to communicate with - * another process, typically a shell. - */ - private FileDescriptor mTermFd; - - /** - * Used to send data to the remote process. - */ - private FileOutputStream mTermOut; - - /** - * The process ID of the remote process. + * The ViewFlipper which holds the collection of EmulatorView widgets. */ - private int mProcId = 0; + private TermViewFlipper mViewFlipper; /** - * A key listener that tracks the modifier keys and allows the full ASCII - * character set to be entered. + * The name of the ViewFlipper in the resources. */ - private TermKeyListener mKeyListener; - - /** - * The name of our emulator view in the view resource. - */ - private static final int EMULATOR_VIEW = R.id.emulatorView; - - private int mStatusBar = 0; - private int mCursorStyle = 0; - private int mCursorBlink = 0; - private int mFontSize = 9; - private int mColorId = 2; - private int mControlKeyId = 5; // Default to Volume Down - private int mFnKeyId = 4; // Default to Volume Up - private int mUseCookedIME = 0; - - private static final String STATUSBAR_KEY = "statusbar"; - private static final String CURSORSTYLE_KEY = "cursorstyle"; - private static final String CURSORBLINK_KEY = "cursorblink"; - private static final String FONTSIZE_KEY = "fontsize"; - private static final String COLOR_KEY = "color"; - private static final String CONTROLKEY_KEY = "controlkey"; - private static final String FNKEY_KEY = "fnkey"; - private static final String IME_KEY = "ime"; - private static final String SHELL_KEY = "shell"; - private static final String INITIALCOMMAND_KEY = "initialcommand"; - - public static final int WHITE = 0xffffffff; - public static final int BLACK = 0xff000000; - public static final int BLUE = 0xff344ebd; - public static final int GREEN = 0xff00ff00; - public static final int AMBER = 0xffffb651; - public static final int RED = 0xffff0113; - - private static final int[][] COLOR_SCHEMES = { - {BLACK, WHITE}, {WHITE, BLACK}, {WHITE, BLUE}, {GREEN, BLACK}, {AMBER, BLACK}, {RED, BLACK}}; - - private static final int[] CONTROL_KEY_SCHEMES = { - KeyEvent.KEYCODE_DPAD_CENTER, - KeyEvent.KEYCODE_AT, - KeyEvent.KEYCODE_ALT_LEFT, - KeyEvent.KEYCODE_ALT_RIGHT, - KeyEvent.KEYCODE_VOLUME_UP, - KeyEvent.KEYCODE_VOLUME_DOWN, - KeyEvent.KEYCODE_CAMERA - }; - private static final String[] CONTROL_KEY_NAME = { - "Ball", "@", "Left-Alt", "Right-Alt", "Vol-Up", "Vol-Dn", "Camera" - }; - private static final int[] FN_KEY_SCHEMES = { - KeyEvent.KEYCODE_DPAD_CENTER, - KeyEvent.KEYCODE_AT, - KeyEvent.KEYCODE_ALT_LEFT, - KeyEvent.KEYCODE_ALT_RIGHT, - KeyEvent.KEYCODE_VOLUME_UP, - KeyEvent.KEYCODE_VOLUME_DOWN, - KeyEvent.KEYCODE_CAMERA - }; - private static final String[] FN_KEY_NAME = { - "Ball", "@", "Left-Alt", "Right-Alt", "Vol-Up", "Vol-Dn", "Camera" - }; + private static final int VIEW_FLIPPER = R.id.view_flipper; - private int mControlKeyCode; - private int mFnKeyCode; - - private final static String DEFAULT_SHELL = "/system/bin/sh -"; - private String mShell; - - private final static String DEFAULT_INITIAL_COMMAND = - "export PATH=/data/local/bin:$PATH"; - private String mInitialCommand; + private ArrayList mTermSessions; private SharedPreferences mPrefs; + private TermSettings mSettings; private final static int SELECT_TEXT_ID = 0; private final static int COPY_ALL_ID = 1; @@ -216,110 +79,86 @@ public class Term extends Activity { private boolean mAlreadyStarted = false; - public TermService mTermService; private Intent TSIntent; + public static final int REQUEST_CHOOSE_WINDOW = 1; + public static final String EXTRA_WINDOW_ID = "jackpal.androidterm2.window_id"; + private int onResumeSelectWindow = -1; + private PowerManager.WakeLock mWakeLock; private WifiManager.WifiLock mWifiLock; + private TermService mTermService; + private ServiceConnection mTSConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + Log.i(TermDebug.LOG_TAG, "Bound to TermService"); + TermService.TSBinder binder = (TermService.TSBinder) service; + mTermService = binder.getService(); + populateViewFlipper(); + } + + public void onServiceDisconnected(ComponentName arg0) { + mTermService = null; + } + }; + @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); - Log.e(Term.LOG_TAG, "onCreate"); + Log.e(TermDebug.LOG_TAG, "onCreate"); mPrefs = PreferenceManager.getDefaultSharedPreferences(this); - readPrefs(); + mSettings = new TermSettings(mPrefs); TSIntent = new Intent(this, TermService.class); startService(TSIntent); - setContentView(R.layout.term_activity); - - mEmulatorView = (EmulatorView) findViewById(EMULATOR_VIEW); - - DisplayMetrics metrics = new DisplayMetrics(); - getWindowManager().getDefaultDisplay().getMetrics(metrics); - mEmulatorView.setScaledDensity(metrics.scaledDensity); - - startListening(); - - mKeyListener = new TermKeyListener(); - - mEmulatorView.setFocusable(true); - mEmulatorView.setFocusableInTouchMode(true); - mEmulatorView.requestFocus(); - mEmulatorView.register(this, mKeyListener); + if (!bindService(TSIntent, mTSConnection, BIND_AUTO_CREATE)) { + Log.w(TermDebug.LOG_TAG, "bind to service failed!"); + } - registerForContextMenu(mEmulatorView); + setContentView(R.layout.term_activity); + mViewFlipper = (TermViewFlipper) findViewById(VIEW_FLIPPER); PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE); - mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, Term.LOG_TAG); + mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TermDebug.LOG_TAG); WifiManager wm = (WifiManager)getSystemService(Context.WIFI_SERVICE); - mWifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL, Term.LOG_TAG); + mWifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL, TermDebug.LOG_TAG); updatePrefs(); mAlreadyStarted = true; } + private void populateViewFlipper() { + if (mTermService != null) { + mTermSessions = mTermService.getSessions(); + + if (mTermSessions.size() == 0) { + mTermSessions.add(createTermSession()); + } + + for (TermSession session : mTermSessions) { + EmulatorView view = createEmulatorView(session); + mViewFlipper.addView(view); + } + + updatePrefs(); + } + } + @Override public void onDestroy() { super.onDestroy(); - if (mProcId != 0) { - Exec.hangupProcessGroup(mProcId); - mProcId = 0; - } - if (mTermFd != null) { - Exec.close(mTermFd); - mTermFd = null; - } + mViewFlipper.removeAllViews(); + unbindService(mTSConnection); + stopService(TSIntent); + mTermService = null; + mTSConnection = null; if (mWakeLock.isHeld()) { mWakeLock.release(); } if (mWifiLock.isHeld()) { mWifiLock.release(); } - stopService(TSIntent); - } - - private void startListening() { - int[] processId = new int[1]; - - createSubprocess(processId); - mProcId = processId[0]; - - final Handler handler = new Handler() { - @Override - public void handleMessage(Message msg) { - } - }; - - Runnable watchForDeath = new Runnable() { - - public void run() { - Log.i(Term.LOG_TAG, "waiting for: " + mProcId); - int result = Exec.waitFor(mProcId); - Log.i(Term.LOG_TAG, "Subprocess exited: " + result); - handler.sendEmptyMessage(result); - } - - }; - Thread watcher = new Thread(watchForDeath); - watcher.start(); - - mTermOut = new FileOutputStream(mTermFd); - - mEmulatorView.initialize(mTermFd, mTermOut); - - sendInitialCommand(); - } - - private void sendInitialCommand() { - String initialCommand = mInitialCommand; - if (initialCommand == null || initialCommand.equals("")) { - initialCommand = DEFAULT_INITIAL_COMMAND; - } - if (initialCommand.length() > 0) { - write(initialCommand + '\r'); - } } private void restart() { @@ -327,132 +166,63 @@ private void restart() { finish(); } - private void write(String data) { - try { - mTermOut.write(data.getBytes()); - mTermOut.flush(); - } catch (IOException e) { - // Ignore exception - // We don't really care if the receiver isn't listening. - // We just make a best effort to answer the query. + private TermSession createTermSession() { + /* Check whether we've received an initial command from the + * launching application + */ + String initialCommand = mSettings.getInitialCommand(); + String iInitialCommand = getIntent().getStringExtra("jackpal.androidterm2.iInitialCommand"); + if (iInitialCommand != null) { + if (initialCommand != null) { + initialCommand += "\r" + iInitialCommand; + } else { + initialCommand = iInitialCommand; + } } + + return new TermSession(mSettings, null, initialCommand); } - private void createSubprocess(int[] processId) { - String shell = mShell; - if (shell == null || shell.equals("")) { - shell = DEFAULT_SHELL; - } - ArrayList args = parse(shell); - String arg0 = args.get(0); - String arg1 = null; - String arg2 = null; - if (args.size() >= 2) { - arg1 = args.get(1); - } - if (args.size() >= 3) { - arg2 = args.get(2); - } - mTermFd = Exec.createSubprocess(arg0, arg1, arg2, processId); + private EmulatorView createEmulatorView(TermSession session) { + DisplayMetrics metrics = new DisplayMetrics(); + getWindowManager().getDefaultDisplay().getMetrics(metrics); + EmulatorView emulatorView = new EmulatorView(this, session, mViewFlipper, metrics); + + FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.WRAP_CONTENT, + FrameLayout.LayoutParams.WRAP_CONTENT, + Gravity.LEFT + ); + emulatorView.setLayoutParams(params); + + session.setUpdateCallback(emulatorView.getUpdateCallback()); + + registerForContextMenu(emulatorView); + + return emulatorView; } - private ArrayList parse(String cmd) { - final int PLAIN = 0; - final int WHITESPACE = 1; - final int INQUOTE = 2; - int state = WHITESPACE; - ArrayList result = new ArrayList(); - int cmdLen = cmd.length(); - StringBuilder builder = new StringBuilder(); - for (int i = 0; i < cmdLen; i++) { - char c = cmd.charAt(i); - if (state == PLAIN) { - if (Character.isWhitespace(c)) { - result.add(builder.toString()); - builder.delete(0,builder.length()); - state = WHITESPACE; - } else if (c == '"') { - state = INQUOTE; - } else { - builder.append(c); - } - } else if (state == WHITESPACE) { - if (Character.isWhitespace(c)) { - // do nothing - } else if (c == '"') { - state = INQUOTE; - } else { - state = PLAIN; - builder.append(c); - } - } else if (state == INQUOTE) { - if (c == '\\') { - if (i + 1 < cmdLen) { - i += 1; - builder.append(cmd.charAt(i)); - } - } else if (c == '"') { - state = PLAIN; - } else { - builder.append(c); - } - } - } - if (builder.length() > 0) { - result.add(builder.toString()); - } - return result; + private TermSession getCurrentTermSession() { + return mTermSessions.get(mViewFlipper.getDisplayedChild()); } - private void readPrefs() { - mStatusBar = readIntPref(STATUSBAR_KEY, mStatusBar, 1); - // mCursorStyle = readIntPref(CURSORSTYLE_KEY, mCursorStyle, 2); - // mCursorBlink = readIntPref(CURSORBLINK_KEY, mCursorBlink, 1); - mFontSize = readIntPref(FONTSIZE_KEY, mFontSize, 20); - mColorId = readIntPref(COLOR_KEY, mColorId, COLOR_SCHEMES.length - 1); - mControlKeyId = readIntPref(CONTROLKEY_KEY, mControlKeyId, - CONTROL_KEY_SCHEMES.length - 1); - mFnKeyId = readIntPref(FNKEY_KEY, mFnKeyId, - FN_KEY_SCHEMES.length - 1); - mUseCookedIME = readIntPref(IME_KEY, mUseCookedIME, 1); - { - String newShell = readStringPref(SHELL_KEY, mShell); - if ((newShell == null) || ! newShell.equals(mShell)) { - if (mShell != null) { - Log.i(Term.LOG_TAG, "New shell set. Restarting."); - restart(); - } - mShell = newShell; - } - } - { - String newInitialCommand = readStringPref(INITIALCOMMAND_KEY, - mInitialCommand); - if ((newInitialCommand == null) - || ! newInitialCommand.equals(mInitialCommand)) { - if (mInitialCommand != null) { - Log.i(Term.LOG_TAG, "New initial command set. Restarting."); - restart(); - } - mInitialCommand = newInitialCommand; - } - } + private EmulatorView getCurrentEmulatorView() { + return (EmulatorView) mViewFlipper.getCurrentView(); } private void updatePrefs() { DisplayMetrics metrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(metrics); - mEmulatorView.setTextSize((int) (mFontSize * metrics.density)); - mEmulatorView.setCursorStyle(mCursorStyle, mCursorBlink); - mEmulatorView.setUseCookedIME(mUseCookedIME != 0); - setColors(); - mControlKeyCode = CONTROL_KEY_SCHEMES[mControlKeyId]; - mFnKeyCode = FN_KEY_SCHEMES[mFnKeyId]; + + for (View v : mViewFlipper) { + ((EmulatorView) v).setDensity(metrics); + ((EmulatorView) v).updatePrefs(mSettings); + } { Window win = getWindow(); WindowManager.LayoutParams params = win.getAttributes(); final int FULLSCREEN = WindowManager.LayoutParams.FLAG_FULLSCREEN; - int desiredFlag = mStatusBar != 0 ? 0 : FULLSCREEN; + int desiredFlag = mSettings.showStatusBar() ? 0 : FULLSCREEN; if (desiredFlag != (params.flags & FULLSCREEN)) { if (mAlreadyStarted) { // Can't switch to/from fullscreen after @@ -469,49 +239,48 @@ private void updatePrefs() { } } - private int readIntPref(String key, int defaultValue, int maxValue) { - int val; - try { - val = Integer.parseInt( - mPrefs.getString(key, Integer.toString(defaultValue))); - } catch (NumberFormatException e) { - val = defaultValue; - } - val = Math.max(0, Math.min(val, maxValue)); - return val; - } - - private String readStringPref(String key, String defaultValue) { - return mPrefs.getString(key, defaultValue); - } - - public int getControlKeyCode() { - return mControlKeyCode; - } - - public int getFnKeyCode() { - return mFnKeyCode; - } - @Override public void onResume() { super.onResume(); - readPrefs(); + + if (mTermSessions != null && mTermSessions.size() < mViewFlipper.getChildCount()) { + for (int i = 0; i < mViewFlipper.getChildCount(); ++i) { + EmulatorView v = (EmulatorView) mViewFlipper.getChildAt(i); + if (!mTermSessions.contains(v.getTermSession())) { + v.onPause(); + mViewFlipper.removeView(v); + --i; + } + } + } + + mPrefs = PreferenceManager.getDefaultSharedPreferences(this); + mSettings.readPrefs(mPrefs); updatePrefs(); - mEmulatorView.onResume(); + + if (onResumeSelectWindow >= 0) { + mViewFlipper.setDisplayedChild(onResumeSelectWindow); + onResumeSelectWindow = -1; + } else { + mViewFlipper.resumeCurrentView(); + } } @Override public void onPause() { super.onPause(); - mEmulatorView.onPause(); + + mViewFlipper.pauseCurrentView(); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - mEmulatorView.updateSize(true); + EmulatorView v = (EmulatorView) mViewFlipper.getCurrentView(); + if (v != null) { + v.updateSize(true); + } } @Override @@ -525,6 +294,12 @@ public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); if (id == R.id.menu_preferences) { doPreferences(); + } else if (id == R.id.menu_new_window) { + doCreateNewWindow(); + } else if (id == R.id.menu_close_window) { + doCloseWindow(); + } else if (id == R.id.menu_window_list) { + startActivityForResult(new Intent(this, WindowList.class), REQUEST_CHOOSE_WINDOW); } else if (id == R.id.menu_reset) { doResetTerminal(); } else if (id == R.id.menu_send_email) { @@ -541,6 +316,64 @@ public boolean onOptionsItemSelected(MenuItem item) { return super.onOptionsItemSelected(item); } + private void doCreateNewWindow() { + if (mTermSessions == null) { + Log.w(TermDebug.LOG_TAG, "Couldn't create new window because mTermSessions == null"); + return; + } + + TermSession session = createTermSession(); + mTermSessions.add(session); + + EmulatorView view = createEmulatorView(session); + view.updatePrefs(mSettings); + + mViewFlipper.addView(view); + mViewFlipper.setDisplayedChild(mViewFlipper.getChildCount()-1); + } + + private void doCloseWindow() { + if (mTermSessions == null) { + return; + } + + EmulatorView view = getCurrentEmulatorView(); + if (view == null) { + return; + } + TermSession session = mTermSessions.remove(mViewFlipper.getDisplayedChild()); + view.onPause(); + session.finish(); + mViewFlipper.removeView(view); + if (mTermSessions.size() == 0) { + finish(); + } else { + mViewFlipper.showNext(); + } + } + + @Override + protected void onActivityResult(int request, int result, Intent data) { + switch (request) { + case REQUEST_CHOOSE_WINDOW: + if (result == RESULT_OK && data != null) { + int position = data.getIntExtra(EXTRA_WINDOW_ID, -2); + if (position >= 0) { + // Switch windows after session list is in sync, not here + onResumeSelectWindow = position; + } else if (position == -1) { + doCreateNewWindow(); + } + } else { + // Close the activity if user closed all sessions + if (mTermSessions.size() == 0) { + finish(); + } + } + break; + } + } + @Override public boolean onPrepareOptionsMenu(Menu menu) { MenuItem wakeLockItem = menu.findItem(R.id.menu_toggle_wakelock); @@ -575,7 +408,7 @@ public void onCreateContextMenu(ContextMenu menu, View v, public boolean onContextItemSelected(MenuItem item) { switch (item.getItemId()) { case SELECT_TEXT_ID: - mEmulatorView.toggleSelectingText(); + getCurrentEmulatorView().toggleSelectingText(); return true; case COPY_ALL_ID: doCopyAll(); @@ -600,11 +433,6 @@ private void doPreferences() { startActivity(new Intent(this, TermPreferences.class)); } - private void setColors() { - int[] scheme = COLOR_SCHEMES[mColorId]; - mEmulatorView.setColors(scheme[0], scheme[1]); - } - private void doResetTerminal() { restart(); } @@ -618,14 +446,14 @@ private void doEmailTranscript() { new Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:" + addr)); - intent.putExtra("body", mEmulatorView.getTranscriptText().trim()); + intent.putExtra("body", getCurrentTermSession().getTranscriptText().trim()); startActivity(intent); } private void doCopyAll() { ClipboardManager clip = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); - clip.setText(mEmulatorView.getTranscriptText().trim()); + clip.setText(getCurrentTermSession().getTranscriptText().trim()); } private void doPaste() { @@ -636,41 +464,43 @@ private void doPaste() { try { utf8 = paste.toString().getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { - Log.e(Term.LOG_TAG, "UTF-8 encoding not found."); + Log.e(TermDebug.LOG_TAG, "UTF-8 encoding not found."); return; } - try { - mTermOut.write(utf8); - } catch (IOException e) { - Log.e(Term.LOG_TAG, "could not write paste text to terminal."); - } + getCurrentTermSession().write(paste.toString()); } private void doDocumentKeys() { - String controlKey = CONTROL_KEY_NAME[mControlKeyId]; - String fnKey = FN_KEY_NAME[mFnKeyId]; - new AlertDialog.Builder(this). - setTitle("Press " + controlKey + " and Key"). - setMessage(controlKey + " Space : Control-@ (NUL)\n" - + controlKey + " A..Z : Control-A..Z\n" - + controlKey + " 0..9 : Control-0..9\n" - + fnKey + " W : Up\n" - + fnKey + " A : Left\n" - + fnKey + " S : Down\n" - + fnKey + " D : Right\n" - + fnKey + " P : PageUp\n" - + fnKey + " N : PageDown\n" - + fnKey + " T : Tab\n" - + fnKey + " L : | (pipe)\n" - + fnKey + " U : _ (underscore)\n" - + fnKey + " E : Control-[ (ESC)\n" - + fnKey + " . : Control-\\\n" - + fnKey + " 2 : Control-_\n" - + fnKey + " 3 : Control-^\n" - + fnKey + " 4 : Control-]" - ).show(); + AlertDialog.Builder dialog = new AlertDialog.Builder(this); + Resources r = getResources(); + dialog.setTitle(r.getString(R.string.control_key_dialog_title)); + dialog.setMessage( + formatMessage(mSettings.getControlKeyId(), TermSettings.CONTROL_KEY_ID_NONE, + r, R.array.control_keys_short_names, + R.string.control_key_dialog_control_text, + R.string.control_key_dialog_control_disabled_text, "CTRLKEY") + + "\n\n" + + formatMessage(mSettings.getFnKeyId(), TermSettings.FN_KEY_ID_NONE, + r, R.array.fn_keys_short_names, + R.string.control_key_dialog_fn_text, + R.string.control_key_dialog_fn_disabled_text, "FNKEY")); + dialog.show(); } + private String formatMessage(int keyId, int disabledKeyId, + Resources r, int arrayId, + int enabledId, + int disabledId, String regex) { + if (keyId == disabledKeyId) { + return r.getString(disabledId); + } + String[] keyNames = r.getStringArray(arrayId); + String keyName = keyNames[keyId]; + String template = r.getString(enabledId); + String result = template.replaceAll(regex, keyName); + return result; + } + private void doToggleSoftKeyboard() { InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); @@ -694,3858 +524,3 @@ private void doToggleWifiLock() { } } } - - -/** - * An abstract screen interface. A terminal screen stores lines of text. (The - * reason to abstract it is to allow different implementations, and to hide - * implementation details from clients.) - */ -interface Screen { - - /** - * Set line wrap flag for a given row. Affects how lines are logically - * wrapped when changing screen size or converting to a transcript. - */ - void setLineWrap(int row); - - /** - * Store byte b into the screen at location (x, y) - * - * @param x X coordinate (also known as column) - * @param y Y coordinate (also known as row) - * @param b ASCII character to store - * @param foreColor the foreground color - * @param backColor the background color - */ - void set(int x, int y, byte b, int foreColor, int backColor); - - /** - * Scroll the screen down one line. To scroll the whole screen of a 24 line - * screen, the arguments would be (0, 24). - * - * @param topMargin First line that is scrolled. - * @param bottomMargin One line after the last line that is scrolled. - */ - void scroll(int topMargin, int bottomMargin, int foreColor, int backColor); - - /** - * Block copy characters from one position in the screen to another. The two - * positions can overlap. All characters of the source and destination must - * be within the bounds of the screen, or else an InvalidParemeterException - * will be thrown. - * - * @param sx source X coordinate - * @param sy source Y coordinate - * @param w width - * @param h height - * @param dx destination X coordinate - * @param dy destination Y coordinate - */ - void blockCopy(int sx, int sy, int w, int h, int dx, int dy); - - /** - * Block set characters. All characters must be within the bounds of the - * screen, or else and InvalidParemeterException will be thrown. Typically - * this is called with a "val" argument of 32 to clear a block of - * characters. - * - * @param sx source X - * @param sy source Y - * @param w width - * @param h height - * @param val value to set. - * @param foreColor the foreground color - * @param backColor the background color - */ - void blockSet(int sx, int sy, int w, int h, int val, int foreColor, int - backColor); - - /** - * Get the contents of the transcript buffer as a text string. - * - * @return the contents of the transcript buffer. - */ - String getTranscriptText(); - - /** - * Get the selected text inside transcript buffer as a text string. - * @param x1 Selection start - * @param y1 Selection start - * @param x2 Selection end - * @param y2 Selection end - * @return the contents of the transcript buffer. - */ - String getSelectedText(int x1, int y1, int x2, int y2); - - /** - * Resize the screen - * @param columns - * @param rows - */ - void resize(int columns, int rows, int foreColor, int backColor); -} - - -/** - * A TranscriptScreen is a screen that remembers data that's been scrolled. The - * old data is stored in a ring buffer to minimize the amount of copying that - * needs to be done. The transcript does its own drawing, to avoid having to - * expose its internal data structures. - */ -class TranscriptScreen implements Screen { - private static final String TAG = "TranscriptScreen"; - - /** - * The width of the transcript, in characters. Fixed at initialization. - */ - private int mColumns; - - /** - * The total number of rows in the transcript and the screen. Fixed at - * initialization. - */ - private int mTotalRows; - - /** - * The number of rows in the active portion of the transcript. Doesn't - * include the screen. - */ - private int mActiveTranscriptRows; - - /** - * Which row is currently the topmost line of the transcript. Used to - * implement a circular buffer. - */ - private int mHead; - - /** - * The number of active rows, includes both the transcript and the screen. - */ - private int mActiveRows; - - /** - * The number of rows in the screen. - */ - private int mScreenRows; - - /** - * The data for both the screen and the transcript. The first mScreenRows * - * mLineWidth characters are the screen, the rest are the transcript. - * The low byte encodes the ASCII character, the high byte encodes the - * foreground and background colors, plus underline and bold. - */ - private char[] mData; - - /** - * The data's stored as color-encoded chars, but the drawing routines require chars, so we - * need a temporary buffer to hold a row's worth of characters. - */ - private char[] mRowBuffer; - - /** - * Flags that keep track of whether the current line logically wraps to the - * next line. This is used when resizing the screen and when copying to the - * clipboard or an email attachment - */ - - private boolean[] mLineWrap; - - /** - * Create a transcript screen. - * - * @param columns the width of the screen in characters. - * @param totalRows the height of the entire text area, in rows of text. - * @param screenRows the height of just the screen, not including the - * transcript that holds lines that have scrolled off the top of the - * screen. - */ - public TranscriptScreen(int columns, int totalRows, int screenRows, - int foreColor, int backColor) { - init(columns, totalRows, screenRows, foreColor, backColor); - } - - private void init(int columns, int totalRows, int screenRows, int foreColor, int backColor) { - mColumns = columns; - mTotalRows = totalRows; - mActiveTranscriptRows = 0; - mHead = 0; - mActiveRows = screenRows; - mScreenRows = screenRows; - int totalSize = columns * totalRows; - mData = new char[totalSize]; - blockSet(0, 0, mColumns, mScreenRows, ' ', foreColor, backColor); - mRowBuffer = new char[columns]; - mLineWrap = new boolean[totalRows]; - consistencyCheck(); - } - - /** - * Convert a row value from the public external coordinate system to our - * internal private coordinate system. External coordinate system: - * -mActiveTranscriptRows to mScreenRows-1, with the screen being - * 0..mScreenRows-1 Internal coordinate system: 0..mScreenRows-1 rows of - * mData are the visible rows. mScreenRows..mActiveRows - 1 are the - * transcript, stored as a circular buffer. - * - * @param row a row in the external coordinate system. - * @return The row corresponding to the input argument in the private - * coordinate system. - */ - private int externalToInternalRow(int row) { - if (row < -mActiveTranscriptRows || row >= mScreenRows) { - String errorMessage = "externalToInternalRow "+ row + - " " + mActiveTranscriptRows + " " + mScreenRows; - Log.e(TAG, errorMessage); - throw new IllegalArgumentException(errorMessage); - } - if (row >= 0) { - return row; // This is a visible row. - } - return mScreenRows - + ((mHead + mActiveTranscriptRows + row) % mActiveTranscriptRows); - } - - private int getOffset(int externalLine) { - return externalToInternalRow(externalLine) * mColumns; - } - - private int getOffset(int x, int y) { - return getOffset(y) + x; - } - - public void setLineWrap(int row) { - mLineWrap[externalToInternalRow(row)] = true; - } - - /** - * Store byte b into the screen at location (x, y) - * - * @param x X coordinate (also known as column) - * @param y Y coordinate (also known as row) - * @param b ASCII character to store - * @param foreColor the foreground color - * @param backColor the background color - */ - public void set(int x, int y, byte b, int foreColor, int backColor) { - mData[getOffset(x, y)] = encode(b, foreColor, backColor); - } - - private char encode(int b, int foreColor, int backColor) { - return (char) ((foreColor << 12) | (backColor << 8) | b); - } - - /** - * Scroll the screen down one line. To scroll the whole screen of a 24 line - * screen, the arguments would be (0, 24). - * - * @param topMargin First line that is scrolled. - * @param bottomMargin One line after the last line that is scrolled. - */ - public void scroll(int topMargin, int bottomMargin, int foreColor, - int backColor) { - // Separate out reasons so that stack crawls help us - // figure out which condition was violated. - if (topMargin > bottomMargin - 1) { - throw new IllegalArgumentException(); - } - - if (topMargin > mScreenRows - 1) { - throw new IllegalArgumentException(); - } - - if (bottomMargin > mScreenRows) { - throw new IllegalArgumentException(); - } - - // Adjust the transcript so that the last line of the transcript - // is ready to receive the newly scrolled data - consistencyCheck(); - int expansionRows = Math.min(1, mTotalRows - mActiveRows); - int rollRows = 1 - expansionRows; - mActiveRows += expansionRows; - mActiveTranscriptRows += expansionRows; - if (mActiveTranscriptRows > 0) { - mHead = (mHead + rollRows) % mActiveTranscriptRows; - } - consistencyCheck(); - - // Block move the scroll line to the transcript - int topOffset = getOffset(topMargin); - int destOffset = getOffset(-1); - System.arraycopy(mData, topOffset, mData, destOffset, mColumns); - - int topLine = externalToInternalRow(topMargin); - int destLine = externalToInternalRow(-1); - System.arraycopy(mLineWrap, topLine, mLineWrap, destLine, 1); - - // Block move the scrolled data up - int numScrollChars = (bottomMargin - topMargin - 1) * mColumns; - System.arraycopy(mData, topOffset + mColumns, mData, topOffset, - numScrollChars); - int numScrollLines = (bottomMargin - topMargin - 1); - System.arraycopy(mLineWrap, topLine + 1, mLineWrap, topLine, - numScrollLines); - - // Erase the bottom line of the scroll region - blockSet(0, bottomMargin - 1, mColumns, 1, ' ', foreColor, backColor); - mLineWrap[externalToInternalRow(bottomMargin-1)] = false; - } - - private void consistencyCheck() { - checkPositive(mColumns); - checkPositive(mTotalRows); - checkRange(0, mActiveTranscriptRows, mTotalRows); - if (mActiveTranscriptRows == 0) { - checkEqual(mHead, 0); - } else { - checkRange(0, mHead, mActiveTranscriptRows-1); - } - checkEqual(mScreenRows + mActiveTranscriptRows, mActiveRows); - checkRange(0, mScreenRows, mTotalRows); - - checkEqual(mTotalRows, mLineWrap.length); - checkEqual(mTotalRows*mColumns, mData.length); - checkEqual(mColumns, mRowBuffer.length); - } - - private void checkPositive(int n) { - if (n < 0) { - throw new IllegalArgumentException("checkPositive " + n); - } - } - - private void checkRange(int a, int b, int c) { - if (a > b || b > c) { - throw new IllegalArgumentException("checkRange " + a + " <= " + b + " <= " + c); - } - } - - private void checkEqual(int a, int b) { - if (a != b) { - throw new IllegalArgumentException("checkEqual " + a + " == " + b); - } - } - - /** - * Block copy characters from one position in the screen to another. The two - * positions can overlap. All characters of the source and destination must - * be within the bounds of the screen, or else an InvalidParemeterException - * will be thrown. - * - * @param sx source X coordinate - * @param sy source Y coordinate - * @param w width - * @param h height - * @param dx destination X coordinate - * @param dy destination Y coordinate - */ - public void blockCopy(int sx, int sy, int w, int h, int dx, int dy) { - if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows - || dx < 0 || dx + w > mColumns || dy < 0 - || dy + h > mScreenRows) { - throw new IllegalArgumentException(); - } - if (sy <= dy) { - // Move in increasing order - for (int y = 0; y < h; y++) { - int srcOffset = getOffset(sx, sy + y); - int dstOffset = getOffset(dx, dy + y); - System.arraycopy(mData, srcOffset, mData, dstOffset, w); - } - } else { - // Move in decreasing order - for (int y = 0; y < h; y++) { - int y2 = h - (y + 1); - int srcOffset = getOffset(sx, sy + y2); - int dstOffset = getOffset(dx, dy + y2); - System.arraycopy(mData, srcOffset, mData, dstOffset, w); - } - } - } - - /** - * Block set characters. All characters must be within the bounds of the - * screen, or else and InvalidParemeterException will be thrown. Typically - * this is called with a "val" argument of 32 to clear a block of - * characters. - * - * @param sx source X - * @param sy source Y - * @param w width - * @param h height - * @param val value to set. - */ - public void blockSet(int sx, int sy, int w, int h, int val, - int foreColor, int backColor) { - if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows) { - throw new IllegalArgumentException(); - } - char[] data = mData; - char encodedVal = encode(val, foreColor, backColor); - for (int y = 0; y < h; y++) { - int offset = getOffset(sx, sy + y); - for (int x = 0; x < w; x++) { - data[offset + x] = encodedVal; - } - } - } - - /** - * Draw a row of text. Out-of-bounds rows are blank, not errors. - * - * @param row The row of text to draw. - * @param canvas The canvas to draw to. - * @param x The x coordinate origin of the drawing - * @param y The y coordinate origin of the drawing - * @param renderer The renderer to use to draw the text - * @param cx the cursor X coordinate, -1 means don't draw it - * @param selx1 the text selection start X coordinate - * @param selx2 the text selection end X coordinate, if equals to selx1 don't draw selection - * @param imeText current IME text, to be rendered at cursor - */ - public final void drawText(int row, Canvas canvas, float x, float y, - TextRenderer renderer, int cx, int selx1, int selx2, String imeText) { - - // Out-of-bounds rows are blank. - if (row < -mActiveTranscriptRows || row >= mScreenRows) { - return; - } - - // Copy the data from the byte array to a char array so they can - // be drawn. - - int offset = getOffset(row); - char[] rowBuffer = mRowBuffer; - char[] data = mData; - int columns = mColumns; - int lastColors = 0; - int lastRunStart = -1; - final int CURSOR_MASK = 0x10000; - for (int i = 0; i < columns; i++) { - char c = data[offset + i]; - int colors = (char) (c & 0xff00); - if (cx == i || (i >= selx1 && i <= selx2)) { - // Set cursor background color: - colors |= CURSOR_MASK; - } - rowBuffer[i] = (char) (c & 0x00ff); - if (colors != lastColors) { - if (lastRunStart >= 0) { - renderer.drawTextRun(canvas, x, y, lastRunStart, rowBuffer, - lastRunStart, i - lastRunStart, - (lastColors & CURSOR_MASK) != 0, - 0xf & (lastColors >> 12), 0xf & (lastColors >> 8)); - } - lastColors = colors; - lastRunStart = i; - } - } - if (lastRunStart >= 0) { - renderer.drawTextRun(canvas, x, y, lastRunStart, rowBuffer, - lastRunStart, columns - lastRunStart, - (lastColors & CURSOR_MASK) != 0, - 0xf & (lastColors >> 12), 0xf & (lastColors >> 8)); - } - - if (cx >= 0 && imeText.length() > 0) { - int imeLength = Math.min(columns, imeText.length()); - int imeOffset = imeText.length() - imeLength; - int imePosition = Math.min(cx, columns - imeLength); - renderer.drawTextRun(canvas, x, y, imePosition, imeText.toCharArray(), - imeOffset, imeLength, true, 0x0f, 0x00); - } - } - - /** - * Get the count of active rows. - * - * @return the count of active rows. - */ - public int getActiveRows() { - return mActiveRows; - } - - /** - * Get the count of active transcript rows. - * - * @return the count of active transcript rows. - */ - public int getActiveTranscriptRows() { - return mActiveTranscriptRows; - } - - public String getTranscriptText() { - return internalGetTranscriptText(true, 0, -mActiveTranscriptRows, mColumns, mScreenRows); - } - - public String getSelectedText(int selX1, int selY1, int selX2, int selY2) { - return internalGetTranscriptText(true, selX1, selY1, selX2, selY2); - } - - private String internalGetTranscriptText(boolean stripColors, int selX1, int selY1, int selX2, int selY2) { - StringBuilder builder = new StringBuilder(); - char[] rowBuffer = mRowBuffer; - char[] data = mData; - int columns = mColumns; - for (int row = -mActiveTranscriptRows; row < mScreenRows; row++) { - int offset = getOffset(row); - int lastPrintingChar = -1; - for (int column = 0; column < columns; column++) { - char c = data[offset + column]; - if (stripColors) { - c = (char) (c & 0xff); - } - if ((c & 0xff) != ' ') { - lastPrintingChar = column; - } - rowBuffer[column] = c; - } - if ( row >= selY1 && row <= selY2 ) { - int x1 = 0; - int x2 = 0; - if ( row == selY1 ) { - x1 = selX1; - } - if ( row == selY2 ) { - x2 = selX2; - } else { - x2 = columns; - } - if (mLineWrap[externalToInternalRow(row)]) { - builder.append(rowBuffer, x1, x2 - x1); - } else { - builder.append(rowBuffer, x1, Math.max(0, Math.min(x2 - x1 + 1, lastPrintingChar + 1 - x1))); - builder.append('\n'); - } - } - } - return builder.toString(); - } - - public void resize(int columns, int rows, int foreColor, int backColor) { - init(columns, mTotalRows, rows, foreColor, backColor); - } -} - -/** - * Renders text into a screen. Contains all the terminal-specific knowlege and - * state. Emulates a subset of the X Window System xterm terminal, which in turn - * is an emulator for a subset of the Digital Equipment Corporation vt100 - * terminal. Missing functionality: text attributes (bold, underline, reverse - * video, color) alternate screen cursor key and keypad escape sequences. - */ -class TerminalEmulator { - - /** - * The cursor row. Numbered 0..mRows-1. - */ - private int mCursorRow; - - /** - * The cursor column. Numbered 0..mColumns-1. - */ - private int mCursorCol; - - /** - * The number of character rows in the terminal screen. - */ - private int mRows; - - /** - * The number of character columns in the terminal screen. - */ - private int mColumns; - - /** - * Used to send data to the remote process. Needed to implement the various - * "report" escape sequences. - */ - private FileOutputStream mTermOut; - - /** - * Stores the characters that appear on the screen of the emulated terminal. - */ - private Screen mScreen; - - /** - * Keeps track of the current argument of the current escape sequence. - * Ranges from 0 to MAX_ESCAPE_PARAMETERS-1. (Typically just 0 or 1.) - */ - private int mArgIndex; - - /** - * The number of parameter arguments. This name comes from the ANSI standard - * for terminal escape codes. - */ - private static final int MAX_ESCAPE_PARAMETERS = 16; - - /** - * Holds the arguments of the current escape sequence. - */ - private int[] mArgs = new int[MAX_ESCAPE_PARAMETERS]; - - // Escape processing states: - - /** - * Escape processing state: Not currently in an escape sequence. - */ - private static final int ESC_NONE = 0; - - /** - * Escape processing state: Have seen an ESC character - */ - private static final int ESC = 1; - - /** - * Escape processing state: Have seen ESC POUND - */ - private static final int ESC_POUND = 2; - - /** - * Escape processing state: Have seen ESC and a character-set-select char - */ - private static final int ESC_SELECT_LEFT_PAREN = 3; - - /** - * Escape processing state: Have seen ESC and a character-set-select char - */ - private static final int ESC_SELECT_RIGHT_PAREN = 4; - - /** - * Escape processing state: ESC [ - */ - private static final int ESC_LEFT_SQUARE_BRACKET = 5; - - /** - * Escape processing state: ESC [ ? - */ - private static final int ESC_LEFT_SQUARE_BRACKET_QUESTION_MARK = 6; - - /** - * True if the current escape sequence should continue, false if the current - * escape sequence should be terminated. Used when parsing a single - * character. - */ - private boolean mContinueSequence; - - /** - * The current state of the escape sequence state machine. - */ - private int mEscapeState; - - /** - * Saved state of the cursor row, Used to implement the save/restore cursor - * position escape sequences. - */ - private int mSavedCursorRow; - - /** - * Saved state of the cursor column, Used to implement the save/restore - * cursor position escape sequences. - */ - private int mSavedCursorCol; - - // DecSet booleans - - /** - * This mask indicates 132-column mode is set. (As opposed to 80-column - * mode.) - */ - private static final int K_132_COLUMN_MODE_MASK = 1 << 3; - - /** - * This mask indicates that origin mode is set. (Cursor addressing is - * relative to the absolute screen size, rather than the currently set top - * and bottom margins.) - */ - private static final int K_ORIGIN_MODE_MASK = 1 << 6; - - /** - * This mask indicates that wraparound mode is set. (As opposed to - * stop-at-right-column mode.) - */ - private static final int K_WRAPAROUND_MODE_MASK = 1 << 7; - - /** - * Holds multiple DECSET flags. The data is stored this way, rather than in - * separate booleans, to make it easier to implement the save-and-restore - * semantics. The various k*ModeMask masks can be used to extract and modify - * the individual flags current states. - */ - private int mDecFlags; - - /** - * Saves away a snapshot of the DECSET flags. Used to implement save and - * restore escape sequences. - */ - private int mSavedDecFlags; - - // Modes set with Set Mode / Reset Mode - - /** - * True if insert mode (as opposed to replace mode) is active. In insert - * mode new characters are inserted, pushing existing text to the right. - */ - private boolean mInsertMode; - - /** - * Automatic newline mode. Configures whether pressing return on the - * keyboard automatically generates a return as well. Not currently - * implemented. - */ - private boolean mAutomaticNewlineMode; - - /** - * An array of tab stops. mTabStop[i] is true if there is a tab stop set for - * column i. - */ - private boolean[] mTabStop; - - // The margins allow portions of the screen to be locked. - - /** - * The top margin of the screen, for scrolling purposes. Ranges from 0 to - * mRows-2. - */ - private int mTopMargin; - - /** - * The bottom margin of the screen, for scrolling purposes. Ranges from - * mTopMargin + 2 to mRows. (Defines the first row after the scrolling - * region. - */ - private int mBottomMargin; - - /** - * True if the next character to be emitted will be automatically wrapped to - * the next line. Used to disambiguate the case where the cursor is - * positioned on column mColumns-1. - */ - private boolean mAboutToAutoWrap; - - /** - * Used for debugging, counts how many chars have been processed. - */ - private int mProcessedCharCount; - - /** - * Foreground color, 0..7, mask with 8 for bold - */ - private int mForeColor; - - /** - * Background color, 0..7, mask with 8 for underline - */ - private int mBackColor; - - private boolean mInverseColors; - - private boolean mbKeypadApplicationMode; - - private boolean mAlternateCharSet; - - /** - * Used for moving selection up along with the scrolling text - */ - private int mScrollCounter = 0; - - /** - * Construct a terminal emulator that uses the supplied screen - * - * @param screen the screen to render characters into. - * @param columns the number of columns to emulate - * @param rows the number of rows to emulate - * @param termOut the output file descriptor that talks to the pseudo-tty. - */ - public TerminalEmulator(Screen screen, int columns, int rows, - FileOutputStream termOut) { - mScreen = screen; - mRows = rows; - mColumns = columns; - mTabStop = new boolean[mColumns]; - mTermOut = termOut; - reset(); - } - - public void updateSize(int columns, int rows) { - if (mRows == rows && mColumns == columns) { - return; - } - if (columns <= 0) { - throw new IllegalArgumentException("rows:" + columns); - } - - if (rows <= 0) { - throw new IllegalArgumentException("rows:" + rows); - } - - String transcriptText = mScreen.getTranscriptText(); - - mScreen.resize(columns, rows, mForeColor, mBackColor); - - if (mRows != rows) { - mRows = rows; - mTopMargin = 0; - mBottomMargin = mRows; - } - if (mColumns != columns) { - int oldColumns = mColumns; - mColumns = columns; - boolean[] oldTabStop = mTabStop; - mTabStop = new boolean[mColumns]; - int toTransfer = Math.min(oldColumns, columns); - System.arraycopy(oldTabStop, 0, mTabStop, 0, toTransfer); - while (mCursorCol >= columns) { - mCursorCol -= columns; - mCursorRow = Math.min(mBottomMargin-1, mCursorRow + 1); - } - } - mCursorRow = 0; - mCursorCol = 0; - mAboutToAutoWrap = false; - - int end = transcriptText.length()-1; - while ((end >= 0) && transcriptText.charAt(end) == '\n') { - end--; - } - for(int i = 0; i <= end; i++) { - byte c = (byte) transcriptText.charAt(i); - if (c == '\n') { - setCursorCol(0); - doLinefeed(); - } else { - emit(c); - } - } - } - - /** - * Get the cursor's current row. - * - * @return the cursor's current row. - */ - public final int getCursorRow() { - return mCursorRow; - } - - /** - * Get the cursor's current column. - * - * @return the cursor's current column. - */ - public final int getCursorCol() { - return mCursorCol; - } - - public final boolean getKeypadApplicationMode() { - return mbKeypadApplicationMode; - } - - private void setDefaultTabStops() { - for (int i = 0; i < mColumns; i++) { - mTabStop[i] = (i & 7) == 0 && i != 0; - } - } - - /** - * Accept bytes (typically from the pseudo-teletype) and process them. - * - * @param buffer a byte array containing the bytes to be processed - * @param base the first index of the array to process - * @param length the number of bytes in the array to process - */ - public void append(byte[] buffer, int base, int length) { - for (int i = 0; i < length; i++) { - byte b = buffer[base + i]; - try { - if (Term.LOG_CHARACTERS_FLAG) { - char printableB = (char) b; - if (b < 32 || b > 126) { - printableB = ' '; - } - Log.w(Term.LOG_TAG, "'" + Character.toString(printableB) - + "' (" + Integer.toString(b) + ")"); - } - process(b); - mProcessedCharCount++; - } catch (Exception e) { - Log.e(Term.LOG_TAG, "Exception while processing character " - + Integer.toString(mProcessedCharCount) + " code " - + Integer.toString(b), e); - } - } - } - - private void process(byte b) { - switch (b) { - case 0: // NUL - // Do nothing - break; - - case 7: // BEL - // Do nothing - break; - - case 8: // BS - setCursorCol(Math.max(0, mCursorCol - 1)); - break; - - case 9: // HT - // Move to next tab stop, but not past edge of screen - setCursorCol(nextTabStop(mCursorCol)); - break; - - case 13: - setCursorCol(0); - break; - - case 10: // CR - case 11: // VT - case 12: // LF - doLinefeed(); - break; - - case 14: // SO: - setAltCharSet(true); - break; - - case 15: // SI: - setAltCharSet(false); - break; - - - case 24: // CAN - case 26: // SUB - if (mEscapeState != ESC_NONE) { - mEscapeState = ESC_NONE; - emit((byte) 127); - } - break; - - case 27: // ESC - // Always starts an escape sequence - startEscapeSequence(ESC); - break; - - case (byte) 0x9b: // CSI - startEscapeSequence(ESC_LEFT_SQUARE_BRACKET); - break; - - default: - mContinueSequence = false; - switch (mEscapeState) { - case ESC_NONE: - if (b >= 32) { - emit(b); - } - break; - - case ESC: - doEsc(b); - break; - - case ESC_POUND: - doEscPound(b); - break; - - case ESC_SELECT_LEFT_PAREN: - doEscSelectLeftParen(b); - break; - - case ESC_SELECT_RIGHT_PAREN: - doEscSelectRightParen(b); - break; - - case ESC_LEFT_SQUARE_BRACKET: - doEscLeftSquareBracket(b); - break; - - case ESC_LEFT_SQUARE_BRACKET_QUESTION_MARK: - doEscLSBQuest(b); - break; - - default: - unknownSequence(b); - break; - } - if (!mContinueSequence) { - mEscapeState = ESC_NONE; - } - break; - } - } - - private void setAltCharSet(boolean alternateCharSet) { - mAlternateCharSet = alternateCharSet; - } - - private int nextTabStop(int cursorCol) { - for (int i = cursorCol; i < mColumns; i++) { - if (mTabStop[i]) { - return i; - } - } - return mColumns - 1; - } - - private void doEscLSBQuest(byte b) { - int mask = getDecFlagsMask(getArg0(0)); - switch (b) { - case 'h': // Esc [ ? Pn h - DECSET - mDecFlags |= mask; - break; - - case 'l': // Esc [ ? Pn l - DECRST - mDecFlags &= ~mask; - break; - - case 'r': // Esc [ ? Pn r - restore - mDecFlags = (mDecFlags & ~mask) | (mSavedDecFlags & mask); - break; - - case 's': // Esc [ ? Pn s - save - mSavedDecFlags = (mSavedDecFlags & ~mask) | (mDecFlags & mask); - break; - - default: - parseArg(b); - break; - } - - // 132 column mode - if ((mask & K_132_COLUMN_MODE_MASK) != 0) { - // We don't actually set 132 cols, but we do want the - // side effect of clearing the screen and homing the cursor. - blockClear(0, 0, mColumns, mRows); - setCursorRowCol(0, 0); - } - - // origin mode - if ((mask & K_ORIGIN_MODE_MASK) != 0) { - // Home the cursor. - setCursorPosition(0, 0); - } - } - - private int getDecFlagsMask(int argument) { - if (argument >= 1 && argument <= 9) { - return (1 << argument); - } - - return 0; - } - - private void startEscapeSequence(int escapeState) { - mEscapeState = escapeState; - mArgIndex = 0; - for (int j = 0; j < MAX_ESCAPE_PARAMETERS; j++) { - mArgs[j] = -1; - } - } - - private void doLinefeed() { - int newCursorRow = mCursorRow + 1; - if (newCursorRow >= mBottomMargin) { - scroll(); - newCursorRow = mBottomMargin - 1; - } - setCursorRow(newCursorRow); - } - - private void continueSequence() { - mContinueSequence = true; - } - - private void continueSequence(int state) { - mEscapeState = state; - mContinueSequence = true; - } - - private void doEscSelectLeftParen(byte b) { - doSelectCharSet(true, b); - } - - private void doEscSelectRightParen(byte b) { - doSelectCharSet(false, b); - } - - private void doSelectCharSet(boolean isG0CharSet, byte b) { - switch (b) { - case 'A': // United Kingdom character set - break; - case 'B': // ASCII set - break; - case '0': // Special Graphics - break; - case '1': // Alternate character set - break; - case '2': - break; - default: - unknownSequence(b); - } - } - - private void doEscPound(byte b) { - switch (b) { - case '8': // Esc # 8 - DECALN alignment test - mScreen.blockSet(0, 0, mColumns, mRows, 'E', - getForeColor(), getBackColor()); - break; - - default: - unknownSequence(b); - break; - } - } - - private void doEsc(byte b) { - switch (b) { - case '#': - continueSequence(ESC_POUND); - break; - - case '(': - continueSequence(ESC_SELECT_LEFT_PAREN); - break; - - case ')': - continueSequence(ESC_SELECT_RIGHT_PAREN); - break; - - case '7': // DECSC save cursor - mSavedCursorRow = mCursorRow; - mSavedCursorCol = mCursorCol; - break; - - case '8': // DECRC restore cursor - setCursorRowCol(mSavedCursorRow, mSavedCursorCol); - break; - - case 'D': // INDEX - doLinefeed(); - break; - - case 'E': // NEL - setCursorCol(0); - doLinefeed(); - break; - - case 'F': // Cursor to lower-left corner of screen - setCursorRowCol(0, mBottomMargin - 1); - break; - - case 'H': // Tab set - mTabStop[mCursorCol] = true; - break; - - case 'M': // Reverse index - if (mCursorRow == 0) { - mScreen.blockCopy(0, mTopMargin + 1, mColumns, mBottomMargin - - (mTopMargin + 1), 0, mTopMargin); - blockClear(0, mBottomMargin - 1, mColumns); - } else { - mCursorRow--; - } - - break; - - case 'N': // SS2 - unimplementedSequence(b); - break; - - case '0': // SS3 - unimplementedSequence(b); - break; - - case 'P': // Device control string - unimplementedSequence(b); - break; - - case 'Z': // return terminal ID - sendDeviceAttributes(); - break; - - case '[': - continueSequence(ESC_LEFT_SQUARE_BRACKET); - break; - - case '=': // DECKPAM - mbKeypadApplicationMode = true; - break; - - case '>' : // DECKPNM - mbKeypadApplicationMode = false; - break; - - default: - unknownSequence(b); - break; - } - } - - private void doEscLeftSquareBracket(byte b) { - switch (b) { - case '@': // ESC [ Pn @ - ICH Insert Characters - { - int charsAfterCursor = mColumns - mCursorCol; - int charsToInsert = Math.min(getArg0(1), charsAfterCursor); - int charsToMove = charsAfterCursor - charsToInsert; - mScreen.blockCopy(mCursorCol, mCursorRow, charsToMove, 1, - mCursorCol + charsToInsert, mCursorRow); - blockClear(mCursorCol, mCursorRow, charsToInsert); - } - break; - - case 'A': // ESC [ Pn A - Cursor Up - setCursorRow(Math.max(mTopMargin, mCursorRow - getArg0(1))); - break; - - case 'B': // ESC [ Pn B - Cursor Down - setCursorRow(Math.min(mBottomMargin - 1, mCursorRow + getArg0(1))); - break; - - case 'C': // ESC [ Pn C - Cursor Right - setCursorCol(Math.min(mColumns - 1, mCursorCol + getArg0(1))); - break; - - case 'D': // ESC [ Pn D - Cursor Left - setCursorCol(Math.max(0, mCursorCol - getArg0(1))); - break; - - case 'G': // ESC [ Pn G - Cursor Horizontal Absolute - setCursorCol(Math.min(Math.max(1, getArg0(1)), mColumns) - 1); - break; - - case 'H': // ESC [ Pn ; H - Cursor Position - setHorizontalVerticalPosition(); - break; - - case 'J': // ESC [ Pn J - Erase in Display - switch (getArg0(0)) { - case 0: // Clear below - blockClear(mCursorCol, mCursorRow, mColumns - mCursorCol); - blockClear(0, mCursorRow + 1, mColumns, - mBottomMargin - (mCursorRow + 1)); - break; - - case 1: // Erase from the start of the screen to the cursor. - blockClear(0, mTopMargin, mColumns, mCursorRow - mTopMargin); - blockClear(0, mCursorRow, mCursorCol + 1); - break; - - case 2: // Clear all - blockClear(0, mTopMargin, mColumns, mBottomMargin - mTopMargin); - break; - - default: - unknownSequence(b); - break; - } - break; - - case 'K': // ESC [ Pn K - Erase in Line - switch (getArg0(0)) { - case 0: // Clear to right - blockClear(mCursorCol, mCursorRow, mColumns - mCursorCol); - break; - - case 1: // Erase start of line to cursor (including cursor) - blockClear(0, mCursorRow, mCursorCol + 1); - break; - - case 2: // Clear whole line - blockClear(0, mCursorRow, mColumns); - break; - - default: - unknownSequence(b); - break; - } - break; - - case 'L': // Insert Lines - { - int linesAfterCursor = mBottomMargin - mCursorRow; - int linesToInsert = Math.min(getArg0(1), linesAfterCursor); - int linesToMove = linesAfterCursor - linesToInsert; - mScreen.blockCopy(0, mCursorRow, mColumns, linesToMove, 0, - mCursorRow + linesToInsert); - blockClear(0, mCursorRow, mColumns, linesToInsert); - } - break; - - case 'M': // Delete Lines - { - int linesAfterCursor = mBottomMargin - mCursorRow; - int linesToDelete = Math.min(getArg0(1), linesAfterCursor); - int linesToMove = linesAfterCursor - linesToDelete; - mScreen.blockCopy(0, mCursorRow + linesToDelete, mColumns, - linesToMove, 0, mCursorRow); - blockClear(0, mCursorRow + linesToMove, mColumns, linesToDelete); - } - break; - - case 'P': // Delete Characters - { - int charsAfterCursor = mColumns - mCursorCol; - int charsToDelete = Math.min(getArg0(1), charsAfterCursor); - int charsToMove = charsAfterCursor - charsToDelete; - mScreen.blockCopy(mCursorCol + charsToDelete, mCursorRow, - charsToMove, 1, mCursorCol, mCursorRow); - blockClear(mCursorCol + charsToMove, mCursorRow, charsToDelete); - } - break; - - case 'T': // Mouse tracking - unimplementedSequence(b); - break; - - case '?': // Esc [ ? -- start of a private mode set - continueSequence(ESC_LEFT_SQUARE_BRACKET_QUESTION_MARK); - break; - - case 'c': // Send device attributes - sendDeviceAttributes(); - break; - - case 'd': // ESC [ Pn d - Vert Position Absolute - setCursorRow(Math.min(Math.max(1, getArg0(1)), mRows) - 1); - break; - - case 'f': // Horizontal and Vertical Position - setHorizontalVerticalPosition(); - break; - - case 'g': // Clear tab stop - switch (getArg0(0)) { - case 0: - mTabStop[mCursorCol] = false; - break; - - case 3: - for (int i = 0; i < mColumns; i++) { - mTabStop[i] = false; - } - break; - - default: - // Specified to have no effect. - break; - } - break; - - case 'h': // Set Mode - doSetMode(true); - break; - - case 'l': // Reset Mode - doSetMode(false); - break; - - case 'm': // Esc [ Pn m - character attributes. - selectGraphicRendition(); - break; - - case 'r': // Esc [ Pn ; Pn r - set top and bottom margins - { - // The top margin defaults to 1, the bottom margin - // (unusually for arguments) defaults to mRows. - // - // The escape sequence numbers top 1..23, but we - // number top 0..22. - // The escape sequence numbers bottom 2..24, and - // so do we (because we use a zero based numbering - // scheme, but we store the first line below the - // bottom-most scrolling line. - // As a result, we adjust the top line by -1, but - // we leave the bottom line alone. - // - // Also require that top + 2 <= bottom - - int top = Math.max(0, Math.min(getArg0(1) - 1, mRows - 2)); - int bottom = Math.max(top + 2, Math.min(getArg1(mRows), mRows)); - mTopMargin = top; - mBottomMargin = bottom; - - // The cursor is placed in the home position - setCursorRowCol(mTopMargin, 0); - } - break; - - default: - parseArg(b); - break; - } - } - - private void selectGraphicRendition() { - for (int i = 0; i <= mArgIndex; i++) { - int code = mArgs[i]; - if ( code < 0) { - if (mArgIndex > 0) { - continue; - } else { - code = 0; - } - } - if (code == 0) { // reset - mInverseColors = false; - mForeColor = 7; - mBackColor = 0; - } else if (code == 1) { // bold - mForeColor |= 0x8; - } else if (code == 4) { // underscore - mBackColor |= 0x8; - } else if (code == 7) { // inverse - mInverseColors = true; - } else if (code >= 30 && code <= 37) { // foreground color - mForeColor = (mForeColor & 0x8) | (code - 30); - } else if (code >= 40 && code <= 47) { // background color - mBackColor = (mBackColor & 0x8) | (code - 40); - } else { - if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) { - Log.w(Term.LOG_TAG, String.format("SGR unknown code %d", code)); - } - } - } - } - - private void blockClear(int sx, int sy, int w) { - blockClear(sx, sy, w, 1); - } - - private void blockClear(int sx, int sy, int w, int h) { - mScreen.blockSet(sx, sy, w, h, ' ', getForeColor(), getBackColor()); - } - - private int getForeColor() { - return mInverseColors ? - ((mBackColor & 0x7) | (mForeColor & 0x8)) : mForeColor; - } - - private int getBackColor() { - return mInverseColors ? - ((mForeColor & 0x7) | (mBackColor & 0x8)) : mBackColor; - } - - private void doSetMode(boolean newValue) { - int modeBit = getArg0(0); - switch (modeBit) { - case 4: - mInsertMode = newValue; - break; - - case 20: - mAutomaticNewlineMode = newValue; - break; - - default: - unknownParameter(modeBit); - break; - } - } - - private void setHorizontalVerticalPosition() { - - // Parameters are Row ; Column - - setCursorPosition(getArg1(1) - 1, getArg0(1) - 1); - } - - private void setCursorPosition(int x, int y) { - int effectiveTopMargin = 0; - int effectiveBottomMargin = mRows; - if ((mDecFlags & K_ORIGIN_MODE_MASK) != 0) { - effectiveTopMargin = mTopMargin; - effectiveBottomMargin = mBottomMargin; - } - int newRow = - Math.max(effectiveTopMargin, Math.min(effectiveTopMargin + y, - effectiveBottomMargin - 1)); - int newCol = Math.max(0, Math.min(x, mColumns - 1)); - setCursorRowCol(newRow, newCol); - } - - private void sendDeviceAttributes() { - // This identifies us as a DEC vt100 with advanced - // video options. This is what the xterm terminal - // emulator sends. - byte[] attributes = - { - /* VT100 */ - (byte) 27, (byte) '[', (byte) '?', (byte) '1', - (byte) ';', (byte) '2', (byte) 'c' - - /* VT220 - (byte) 27, (byte) '[', (byte) '?', (byte) '6', - (byte) '0', (byte) ';', - (byte) '1', (byte) ';', - (byte) '2', (byte) ';', - (byte) '6', (byte) ';', - (byte) '8', (byte) ';', - (byte) '9', (byte) ';', - (byte) '1', (byte) '5', (byte) ';', - (byte) 'c' - */ - }; - - write(attributes); - } - - /** - * Send data to the shell process - * @param data - */ - private void write(byte[] data) { - try { - mTermOut.write(data); - mTermOut.flush(); - } catch (IOException e) { - // Ignore exception - // We don't really care if the receiver isn't listening. - // We just make a best effort to answer the query. - } - } - - private void scroll() { - //System.out.println("Scroll(): mTopMargin " + mTopMargin + " mBottomMargin " + mBottomMargin); - mScrollCounter ++; - mScreen.scroll(mTopMargin, mBottomMargin, - getForeColor(), getBackColor()); - } - - /** - * Process the next ASCII character of a parameter. - * - * @param b The next ASCII character of the paramater sequence. - */ - private void parseArg(byte b) { - if (b >= '0' && b <= '9') { - if (mArgIndex < mArgs.length) { - int oldValue = mArgs[mArgIndex]; - int thisDigit = b - '0'; - int value; - if (oldValue >= 0) { - value = oldValue * 10 + thisDigit; - } else { - value = thisDigit; - } - mArgs[mArgIndex] = value; - } - continueSequence(); - } else if (b == ';') { - if (mArgIndex < mArgs.length) { - mArgIndex++; - } - continueSequence(); - } else { - unknownSequence(b); - } - } - - private int getArg0(int defaultValue) { - return getArg(0, defaultValue); - } - - private int getArg1(int defaultValue) { - return getArg(1, defaultValue); - } - - private int getArg(int index, int defaultValue) { - int result = mArgs[index]; - if (result < 0) { - result = defaultValue; - } - return result; - } - - private void unimplementedSequence(byte b) { - if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) { - logError("unimplemented", b); - } - finishSequence(); - } - - private void unknownSequence(byte b) { - if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) { - logError("unknown", b); - } - finishSequence(); - } - - private void unknownParameter(int parameter) { - if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) { - StringBuilder buf = new StringBuilder(); - buf.append("Unknown parameter"); - buf.append(parameter); - logError(buf.toString()); - } - } - - private void logError(String errorType, byte b) { - if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) { - StringBuilder buf = new StringBuilder(); - buf.append(errorType); - buf.append(" sequence "); - buf.append(" EscapeState: "); - buf.append(mEscapeState); - buf.append(" char: '"); - buf.append((char) b); - buf.append("' ("); - buf.append(b); - buf.append(")"); - boolean firstArg = true; - for (int i = 0; i <= mArgIndex; i++) { - int value = mArgs[i]; - if (value >= 0) { - if (firstArg) { - firstArg = false; - buf.append("args = "); - } - buf.append(String.format("%d; ", value)); - } - } - logError(buf.toString()); - } - } - - private void logError(String error) { - if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) { - Log.e(Term.LOG_TAG, error); - } - finishSequence(); - } - - private void finishSequence() { - mEscapeState = ESC_NONE; - } - - private boolean autoWrapEnabled() { - // Always enable auto wrap, because it's useful on a small screen - return true; - // return (mDecFlags & K_WRAPAROUND_MODE_MASK) != 0; - } - - /** - * Send an ASCII character to the screen. - * - * @param b the ASCII character to display. - */ - private void emit(byte b) { - boolean autoWrap = autoWrapEnabled(); - - if (autoWrap) { - if (mCursorCol == mColumns - 1 && mAboutToAutoWrap) { - mScreen.setLineWrap(mCursorRow); - mCursorCol = 0; - if (mCursorRow + 1 < mBottomMargin) { - mCursorRow++; - } else { - scroll(); - } - } - } - - if (mInsertMode) { // Move character to right one space - int destCol = mCursorCol + 1; - if (destCol < mColumns) { - mScreen.blockCopy(mCursorCol, mCursorRow, mColumns - destCol, - 1, destCol, mCursorRow); - } - } - - mScreen.set(mCursorCol, mCursorRow, b, getForeColor(), getBackColor()); - - if (autoWrap) { - mAboutToAutoWrap = (mCursorCol == mColumns - 1); - } - - mCursorCol = Math.min(mCursorCol + 1, mColumns - 1); - } - - private void setCursorRow(int row) { - mCursorRow = row; - mAboutToAutoWrap = false; - } - - private void setCursorCol(int col) { - mCursorCol = col; - mAboutToAutoWrap = false; - } - - private void setCursorRowCol(int row, int col) { - mCursorRow = Math.min(row, mRows-1); - mCursorCol = Math.min(col, mColumns-1); - mAboutToAutoWrap = false; - } - - public int getScrollCounter() { - return mScrollCounter; - } - - public void clearScrollCounter() { - mScrollCounter = 0; - } - - /** - * Reset the terminal emulator to its initial state. - */ - public void reset() { - mCursorRow = 0; - mCursorCol = 0; - mArgIndex = 0; - mContinueSequence = false; - mEscapeState = ESC_NONE; - mSavedCursorRow = 0; - mSavedCursorCol = 0; - mDecFlags = 0; - mSavedDecFlags = 0; - mInsertMode = false; - mAutomaticNewlineMode = false; - mTopMargin = 0; - mBottomMargin = mRows; - mAboutToAutoWrap = false; - mForeColor = 7; - mBackColor = 0; - mInverseColors = false; - mbKeypadApplicationMode = false; - mAlternateCharSet = false; - // mProcessedCharCount is preserved unchanged. - setDefaultTabStops(); - blockClear(0, 0, mColumns, mRows); - } - - public String getTranscriptText() { - return mScreen.getTranscriptText(); - } - public String getSelectedText(int x1, int y1, int x2, int y2) { - return mScreen.getSelectedText(x1, y1, x2, y2); - } -} - -/** - * Text renderer interface - */ - -interface TextRenderer { - int getCharacterWidth(); - int getCharacterHeight(); - void drawTextRun(Canvas canvas, float x, float y, - int lineOffset, char[] text, - int index, int count, boolean cursor, int foreColor, int backColor); -} - -abstract class BaseTextRenderer implements TextRenderer { - protected int[] mForePaint = { - 0xff000000, // Black - 0xffff0000, // Red - 0xff00ff00, // green - 0xffffff00, // yellow - 0xff0000ff, // blue - 0xffff00ff, // magenta - 0xff00ffff, // cyan - 0xffffffff // white -- is overridden by constructor - }; - protected int[] mBackPaint = { - 0xff000000, // Black -- is overridden by constructor - 0xffcc0000, // Red - 0xff00cc00, // green - 0xffcccc00, // yellow - 0xff0000cc, // blue - 0xffff00cc, // magenta - 0xff00cccc, // cyan - 0xffffffff // white - }; - protected final static int mCursorPaint = 0xff808080; - - public BaseTextRenderer(int forePaintColor, int backPaintColor) { - mForePaint[7] = forePaintColor; - mBackPaint[0] = backPaintColor; - - } -} - -class Bitmap4x8FontRenderer extends BaseTextRenderer { - private final static int kCharacterWidth = 4; - private final static int kCharacterHeight = 8; - private Bitmap mFont; - private int mCurrentForeColor; - private int mCurrentBackColor; - private float[] mColorMatrix; - private Paint mPaint; - private static final float BYTE_SCALE = 1.0f / 255.0f; - - public Bitmap4x8FontRenderer(Resources resources, - int forePaintColor, int backPaintColor) { - super(forePaintColor, backPaintColor); - mFont = BitmapFactory.decodeResource(resources, - R.drawable.atari_small); - mPaint = new Paint(); - mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); - } - - public int getCharacterWidth() { - return kCharacterWidth; - } - - public int getCharacterHeight() { - return kCharacterHeight; - } - - public void drawTextRun(Canvas canvas, float x, float y, - int lineOffset, char[] text, int index, int count, - boolean cursor, int foreColor, int backColor) { - setColorMatrix(mForePaint[foreColor & 7], - cursor ? mCursorPaint : mBackPaint[backColor & 7]); - int destX = (int) x + kCharacterWidth * lineOffset; - int destY = (int) y; - Rect srcRect = new Rect(); - Rect destRect = new Rect(); - destRect.top = (destY - kCharacterHeight); - destRect.bottom = destY; - for(int i = 0; i < count; i++) { - char c = text[i + index]; - if ((cursor || (c != 32)) && (c < 128)) { - int cellX = c & 31; - int cellY = (c >> 5) & 3; - int srcX = cellX * kCharacterWidth; - int srcY = cellY * kCharacterHeight; - srcRect.set(srcX, srcY, - srcX + kCharacterWidth, srcY + kCharacterHeight); - destRect.left = destX; - destRect.right = destX + kCharacterWidth; - canvas.drawBitmap(mFont, srcRect, destRect, mPaint); - } - destX += kCharacterWidth; - } - } - - private void setColorMatrix(int foreColor, int backColor) { - if ((foreColor != mCurrentForeColor) - || (backColor != mCurrentBackColor) - || (mColorMatrix == null)) { - mCurrentForeColor = foreColor; - mCurrentBackColor = backColor; - if (mColorMatrix == null) { - mColorMatrix = new float[20]; - mColorMatrix[18] = 1.0f; // Just copy Alpha - } - for (int component = 0; component < 3; component++) { - int rightShift = (2 - component) << 3; - int fore = 0xff & (foreColor >> rightShift); - int back = 0xff & (backColor >> rightShift); - int delta = back - fore; - mColorMatrix[component * 6] = delta * BYTE_SCALE; - mColorMatrix[component * 5 + 4] = fore; - } - mPaint.setColorFilter(new ColorMatrixColorFilter(mColorMatrix)); - } - } -} - -class PaintRenderer extends BaseTextRenderer { - public PaintRenderer(int fontSize, int forePaintColor, int backPaintColor) { - super(forePaintColor, backPaintColor); - mTextPaint = new Paint(); - mTextPaint.setTypeface(Typeface.MONOSPACE); - mTextPaint.setAntiAlias(true); - mTextPaint.setTextSize(fontSize); - - mCharHeight = (int) Math.ceil(mTextPaint.getFontSpacing()); - mCharAscent = (int) Math.ceil(mTextPaint.ascent()); - mCharDescent = mCharHeight + mCharAscent; - mCharWidth = (int) mTextPaint.measureText(EXAMPLE_CHAR, 0, 1); - } - - public void drawTextRun(Canvas canvas, float x, float y, int lineOffset, - char[] text, int index, int count, - boolean cursor, int foreColor, int backColor) { - if (cursor) { - mTextPaint.setColor(mCursorPaint); - } else { - mTextPaint.setColor(mBackPaint[backColor & 0x7]); - } - float left = x + lineOffset * mCharWidth; - canvas.drawRect(left, y + mCharAscent, - left + count * mCharWidth, y + mCharDescent, - mTextPaint); - boolean bold = ( foreColor & 0x8 ) != 0; - boolean underline = (backColor & 0x8) != 0; - if (bold) { - mTextPaint.setFakeBoldText(true); - } - if (underline) { - mTextPaint.setUnderlineText(true); - } - mTextPaint.setColor(mForePaint[foreColor & 0x7]); - canvas.drawText(text, index, count, left, y, mTextPaint); - if (bold) { - mTextPaint.setFakeBoldText(false); - } - if (underline) { - mTextPaint.setUnderlineText(false); - } - } - - public int getCharacterHeight() { - return mCharHeight; - } - - public int getCharacterWidth() { - return mCharWidth; - } - - - private Paint mTextPaint; - private int mCharWidth; - private int mCharHeight; - private int mCharAscent; - private int mCharDescent; - private static final char[] EXAMPLE_CHAR = {'X'}; - } - -/** - * A multi-thread-safe produce-consumer byte array. - * Only allows one producer and one consumer. - */ - -class ByteQueue { - public ByteQueue(int size) { - mBuffer = new byte[size]; - } - - public int getBytesAvailable() { - synchronized(this) { - return mStoredBytes; - } - } - - public int read(byte[] buffer, int offset, int length) - throws InterruptedException { - if (length + offset > buffer.length) { - throw - new IllegalArgumentException("length + offset > buffer.length"); - } - if (length < 0) { - throw - new IllegalArgumentException("length < 0"); - - } - if (length == 0) { - return 0; - } - synchronized(this) { - while (mStoredBytes == 0) { - wait(); - } - int totalRead = 0; - int bufferLength = mBuffer.length; - boolean wasFull = bufferLength == mStoredBytes; - while (length > 0 && mStoredBytes > 0) { - int oneRun = Math.min(bufferLength - mHead, mStoredBytes); - int bytesToCopy = Math.min(length, oneRun); - System.arraycopy(mBuffer, mHead, buffer, offset, bytesToCopy); - mHead += bytesToCopy; - if (mHead >= bufferLength) { - mHead = 0; - } - mStoredBytes -= bytesToCopy; - length -= bytesToCopy; - offset += bytesToCopy; - totalRead += bytesToCopy; - } - if (wasFull) { - notify(); - } - return totalRead; - } - } - - public void write(byte[] buffer, int offset, int length) - throws InterruptedException { - if (length + offset > buffer.length) { - throw - new IllegalArgumentException("length + offset > buffer.length"); - } - if (length < 0) { - throw - new IllegalArgumentException("length < 0"); - - } - if (length == 0) { - return; - } - synchronized(this) { - int bufferLength = mBuffer.length; - boolean wasEmpty = mStoredBytes == 0; - while (length > 0) { - while(bufferLength == mStoredBytes) { - wait(); - } - int tail = mHead + mStoredBytes; - int oneRun; - if (tail >= bufferLength) { - tail = tail - bufferLength; - oneRun = mHead - tail; - } else { - oneRun = bufferLength - tail; - } - int bytesToCopy = Math.min(oneRun, length); - System.arraycopy(buffer, offset, mBuffer, tail, bytesToCopy); - offset += bytesToCopy; - mStoredBytes += bytesToCopy; - length -= bytesToCopy; - } - if (wasEmpty) { - notify(); - } - } - } - - private byte[] mBuffer; - private int mHead; - private int mStoredBytes; -} -/** - * A view on a transcript and a terminal emulator. Displays the text of the - * transcript and the current cursor position of the terminal emulator. - */ -class EmulatorView extends View implements GestureDetector.OnGestureListener { - - private final String TAG = "EmulatorView"; - private final boolean LOG_KEY_EVENTS = Term.DEBUG && false; - - private Term mTerm; - - /** - * We defer some initialization until we have been layed out in the view - * hierarchy. The boolean tracks when we know what our size is. - */ - private boolean mKnownSize; - - private int mVisibleWidth; - private int mVisibleHeight; - private Rect mVisibleRect = new Rect(); - - /** - * Our transcript. Contains the screen and the transcript. - */ - private TranscriptScreen mTranscriptScreen; - - /** - * Number of rows in the transcript. - */ - private static final int TRANSCRIPT_ROWS = 10000; - - /** - * Total width of each character, in pixels - */ - private int mCharacterWidth; - - /** - * Total height of each character, in pixels - */ - private int mCharacterHeight; - - /** - * Used to render text - */ - private TextRenderer mTextRenderer; - - /** - * Text size. Zero means 4 x 8 font. - */ - private int mTextSize; - - private int mCursorStyle; - private int mCursorBlink; - - /** - * Foreground color. - */ - private int mForeground; - - /** - * Background color. - */ - private int mBackground; - - /** - * Used to paint the cursor - */ - private Paint mCursorPaint; - - private Paint mBackgroundPaint; - - private boolean mUseCookedIme; - - /** - * Our terminal emulator. We use this to get the current cursor position. - */ - private TerminalEmulator mEmulator; - - /** - * The number of rows of text to display. - */ - private int mRows; - - /** - * The number of columns of text to display. - */ - private int mColumns; - - /** - * The number of columns that are visible on the display. - */ - - private int mVisibleColumns; - - /** - * The top row of text to display. Ranges from -activeTranscriptRows to 0 - */ - private int mTopRow; - - private int mLeftColumn; - - private FileDescriptor mTermFd; - /** - * Used to receive data from the remote process. - */ - private FileInputStream mTermIn; - - private FileOutputStream mTermOut; - - private ByteQueue mByteQueue; - - /** - * Used to temporarily hold data received from the remote process. Allocated - * once and used permanently to minimize heap thrashing. - */ - private byte[] mReceiveBuffer; - - /** - * Our private message id, which we use to receive new input from the - * remote process. - */ - private static final int UPDATE = 1; - - private static final int SCREEN_CHECK_PERIOD = 1000; - private static final int CURSOR_BLINK_PERIOD = 1000; - - private boolean mCursorVisible = true; - - private boolean mIsSelectingText = false; - - - private float mScaledDensity; - private static final int SELECT_TEXT_OFFSET_Y = -40; - private int mSelXAnchor = -1; - private int mSelYAnchor = -1; - private int mSelX1 = -1; - private int mSelY1 = -1; - private int mSelX2 = -1; - private int mSelY2 = -1; - - /** - * Used to poll if the view has changed size. Wish there was a better way to do this. - */ - private Runnable mCheckSize = new Runnable() { - - public void run() { - updateSize(false); - mHandler.postDelayed(this, SCREEN_CHECK_PERIOD); - } - }; - - private Runnable mBlinkCursor = new Runnable() { - public void run() { - if (mCursorBlink != 0) { - mCursorVisible = ! mCursorVisible; - mHandler.postDelayed(this, CURSOR_BLINK_PERIOD); - } else { - mCursorVisible = true; - } - // Perhaps just invalidate the character with the cursor. - invalidate(); - } - }; - - /** - * Thread that polls for input from the remote process - */ - - private Thread mPollingThread; - - private GestureDetector mGestureDetector; - private float mScrollRemainder; - private TermKeyListener mKeyListener; - - private String mImeBuffer = ""; - - /** - * Our message handler class. Implements a periodic callback. - */ - private final Handler mHandler = new Handler() { - /** - * Handle the callback message. Call our enclosing class's update - * method. - * - * @param msg The callback message. - */ - @Override - public void handleMessage(Message msg) { - if (msg.what == UPDATE) { - update(); - } - } - }; - - public EmulatorView(Context context) { - super(context); - commonConstructor(); - } - - public void setScaledDensity(float scaledDensity) { - mScaledDensity = scaledDensity; - } - - public void onResume() { - updateSize(false); - mHandler.postDelayed(mCheckSize, SCREEN_CHECK_PERIOD); - if (mCursorBlink != 0) { - mHandler.postDelayed(mBlinkCursor, CURSOR_BLINK_PERIOD); - } - } - - public void onPause() { - mHandler.removeCallbacks(mCheckSize); - if (mCursorBlink != 0) { - mHandler.removeCallbacks(mBlinkCursor); - } - } - - public void register(Term term, TermKeyListener listener) { - mTerm = term; - mKeyListener = listener; - } - - public void setColors(int foreground, int background) { - mForeground = foreground; - mBackground = background; - updateText(); - } - - public String getTranscriptText() { - return mEmulator.getTranscriptText(); - } - - public void resetTerminal() { - mEmulator.reset(); - invalidate(); - } - - @Override - public boolean onCheckIsTextEditor() { - return true; - } - - @Override - public InputConnection onCreateInputConnection(EditorInfo outAttrs) { - outAttrs.inputType = mUseCookedIme ? - EditorInfo.TYPE_CLASS_TEXT : - EditorInfo.TYPE_NULL; - return new InputConnection() { - private boolean mInBatchEdit; - /** - * Used to handle composing text requests - */ - private int mCursor; - private int mComposingTextStart; - private int mComposingTextEnd; - private int mSelectedTextStart; - private int mSelectedTextEnd; - - private void sendChar(int c) { - try { - mapAndSend(c); - } catch (IOException ex) { - - } - } - - private void sendText(CharSequence text) { - int n = text.length(); - try { - for(int i = 0; i < n; i++) { - char c = text.charAt(i); - mapAndSend(c); - } - mTermOut.flush(); - } catch (IOException e) { - Log.e(TAG, "error writing ", e); - } - } - - private void mapAndSend(int c) throws IOException { - int result = mKeyListener.mapControlChar(c); - if (result < TermKeyListener.KEYCODE_OFFSET) { - mTermOut.write(result); - } else { - mKeyListener.handleKeyCode(result - TermKeyListener.KEYCODE_OFFSET, mTermOut, getKeypadApplicationMode()); - } - } - - public boolean beginBatchEdit() { - if (Term.LOG_IME) { - Log.w(TAG, "beginBatchEdit"); - } - setImeBuffer(""); - mCursor = 0; - mComposingTextStart = 0; - mComposingTextEnd = 0; - mInBatchEdit = true; - return true; - } - - public boolean clearMetaKeyStates(int arg0) { - if (Term.LOG_IME) { - Log.w(TAG, "clearMetaKeyStates " + arg0); - } - return false; - } - - public boolean commitCompletion(CompletionInfo arg0) { - if (Term.LOG_IME) { - Log.w(TAG, "commitCompletion " + arg0); - } - return false; - } - - public boolean endBatchEdit() { - if (Term.LOG_IME) { - Log.w(TAG, "endBatchEdit"); - } - mInBatchEdit = false; - return true; - } - - public boolean finishComposingText() { - if (Term.LOG_IME) { - Log.w(TAG, "finishComposingText"); - } - sendText(mImeBuffer); - setImeBuffer(""); - mComposingTextStart = 0; - mComposingTextEnd = 0; - mCursor = 0; - return true; - } - - public int getCursorCapsMode(int arg0) { - if (Term.LOG_IME) { - Log.w(TAG, "getCursorCapsMode(" + arg0 + ")"); - } - return 0; - } - - public ExtractedText getExtractedText(ExtractedTextRequest arg0, - int arg1) { - if (Term.LOG_IME) { - Log.w(TAG, "getExtractedText" + arg0 + "," + arg1); - } - return null; - } - - public CharSequence getTextAfterCursor(int n, int flags) { - if (Term.LOG_IME) { - Log.w(TAG, "getTextAfterCursor(" + n + "," + flags + ")"); - } - int len = Math.min(n, mImeBuffer.length() - mCursor); - if (len <= 0 || mCursor < 0 || mCursor >= mImeBuffer.length()) { - return ""; - } - return mImeBuffer.substring(mCursor, mCursor + len); - } - - public CharSequence getTextBeforeCursor(int n, int flags) { - if (Term.LOG_IME) { - Log.w(TAG, "getTextBeforeCursor(" + n + "," + flags + ")"); - } - int len = Math.min(n, mCursor); - if (len <= 0 || mCursor < 0 || mCursor >= mImeBuffer.length()) { - return ""; - } - return mImeBuffer.substring(mCursor-len, mCursor); - } - - public boolean performContextMenuAction(int arg0) { - if (Term.LOG_IME) { - Log.w(TAG, "performContextMenuAction" + arg0); - } - return true; - } - - public boolean performPrivateCommand(String arg0, Bundle arg1) { - if (Term.LOG_IME) { - Log.w(TAG, "performPrivateCommand" + arg0 + "," + arg1); - } - return true; - } - - public boolean reportFullscreenMode(boolean arg0) { - if (Term.LOG_IME) { - Log.w(TAG, "reportFullscreenMode" + arg0); - } - return true; - } - - public boolean commitText(CharSequence text, int newCursorPosition) { - if (Term.LOG_IME) { - Log.w(TAG, "commitText(\"" + text + "\", " + newCursorPosition + ")"); - } - clearComposingText(); - sendText(text); - setImeBuffer(""); - mCursor = 0; - return true; - } - - private void clearComposingText() { - setImeBuffer(mImeBuffer.substring(0, mComposingTextStart) + - mImeBuffer.substring(mComposingTextEnd)); - if (mCursor < mComposingTextStart) { - // do nothing - } else if (mCursor < mComposingTextEnd) { - mCursor = mComposingTextStart; - } else { - mCursor -= mComposingTextEnd - mComposingTextStart; - } - mComposingTextEnd = mComposingTextStart = 0; - } - - public boolean deleteSurroundingText(int leftLength, int rightLength) { - if (Term.LOG_IME) { - Log.w(TAG, "deleteSurroundingText(" + leftLength + - "," + rightLength + ")"); - } - if (leftLength > 0) { - for (int i = 0; i < leftLength; i++) { - sendKeyEvent( - new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)); - } - } else if ((leftLength == 0) && (rightLength == 0)) { - // Delete key held down / repeating - sendKeyEvent( - new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)); - } - // TODO: handle forward deletes. - return true; - } - - public boolean performEditorAction(int actionCode) { - if (Term.LOG_IME) { - Log.w(TAG, "performEditorAction(" + actionCode + ")"); - } - if (actionCode == EditorInfo.IME_ACTION_UNSPECIFIED) { - // The "return" key has been pressed on the IME. - sendText("\n"); - } - return true; - } - - public boolean sendKeyEvent(KeyEvent event) { - if (Term.LOG_IME) { - Log.w(TAG, "sendKeyEvent(" + event + ")"); - } - // Some keys are sent here rather than to commitText. - // In particular, del and the digit keys are sent here. - // (And I have reports that the HTC Magic also sends Return here.) - // As a bit of defensive programming, handle every key. - dispatchKeyEvent(event); - return true; - } - - public boolean setComposingText(CharSequence text, int newCursorPosition) { - if (Term.LOG_IME) { - Log.w(TAG, "setComposingText(\"" + text + "\", " + newCursorPosition + ")"); - } - setImeBuffer(mImeBuffer.substring(0, mComposingTextStart) + - text + mImeBuffer.substring(mComposingTextEnd)); - mComposingTextEnd = mComposingTextStart + text.length(); - mCursor = newCursorPosition > 0 ? mComposingTextEnd + newCursorPosition - 1 - : mComposingTextStart - newCursorPosition; - return true; - } - - public boolean setSelection(int start, int end) { - if (Term.LOG_IME) { - Log.w(TAG, "setSelection" + start + "," + end); - } - int length = mImeBuffer.length(); - if (start == end && start > 0 && start < length) { - mSelectedTextStart = mSelectedTextEnd = 0; - mCursor = start; - } else if (start < end && start > 0 && end < length) { - mSelectedTextStart = start; - mSelectedTextEnd = end; - mCursor = start; - } - return true; - } - - public boolean setComposingRegion(int start, int end) { - if (Term.LOG_IME) { - Log.w(TAG, "setComposingRegion " + start + "," + end); - } - if (start < end && start > 0 && end < mImeBuffer.length()) { - clearComposingText(); - mComposingTextStart = start; - mComposingTextEnd = end; - } - return true; - } - - public CharSequence getSelectedText(int flags) { - if (Term.LOG_IME) { - Log.w(TAG, "getSelectedText " + flags); - } - return mImeBuffer.substring(mSelectedTextStart, mSelectedTextEnd+1); - } - - }; - } - - private void setImeBuffer(String buffer) { - if (!buffer.equals(mImeBuffer)) { - invalidate(); - } - mImeBuffer = buffer; - } - - public boolean getKeypadApplicationMode() { - return mEmulator.getKeypadApplicationMode(); - } - - public EmulatorView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public EmulatorView(Context context, AttributeSet attrs, - int defStyle) { - super(context, attrs, defStyle); - // TypedArray a = - // context.obtainStyledAttributes(android.R.styleable.View); - // initializeScrollbars(a); - // a.recycle(); - commonConstructor(); - } - - private void commonConstructor() { - mTextRenderer = null; - mCursorPaint = new Paint(); - mCursorPaint.setARGB(255,128,128,128); - mBackgroundPaint = new Paint(); - mTopRow = 0; - mLeftColumn = 0; - mGestureDetector = new GestureDetector(this); - // mGestureDetector.setIsLongpressEnabled(false); - setVerticalScrollBarEnabled(true); - } - - @Override - protected int computeVerticalScrollRange() { - return mTranscriptScreen.getActiveRows(); - } - - @Override - protected int computeVerticalScrollExtent() { - return mRows; - } - - @Override - protected int computeVerticalScrollOffset() { - return mTranscriptScreen.getActiveRows() + mTopRow - mRows; - } - - /** - * Call this to initialize the view. - * - * @param termFd the file descriptor - * @param termOut the output stream for the pseudo-teletype - */ - public void initialize(FileDescriptor termFd, FileOutputStream termOut) { - mTermOut = termOut; - mTermFd = termFd; - mTextSize = 10; - mForeground = Term.WHITE; - mBackground = Term.BLACK; - updateText(); - mTermIn = new FileInputStream(mTermFd); - mReceiveBuffer = new byte[4 * 1024]; - mByteQueue = new ByteQueue(4 * 1024); - } - - /** - * Accept a sequence of bytes (typically from the pseudo-tty) and process - * them. - * - * @param buffer a byte array containing bytes to be processed - * @param base the index of the first byte in the buffer to process - * @param length the number of bytes to process - */ - public void append(byte[] buffer, int base, int length) { - mEmulator.append(buffer, base, length); - if ( mIsSelectingText ) { - int rowShift = mEmulator.getScrollCounter(); - mSelY1 -= rowShift; - mSelY2 -= rowShift; - mSelYAnchor -= rowShift; - } - mEmulator.clearScrollCounter(); - ensureCursorVisible(); - invalidate(); - } - - /** - * Page the terminal view (scroll it up or down by delta screenfulls.) - * - * @param delta the number of screens to scroll. Positive means scroll down, - * negative means scroll up. - */ - public void page(int delta) { - mTopRow = - Math.min(0, Math.max(-(mTranscriptScreen - .getActiveTranscriptRows()), mTopRow + mRows * delta)); - invalidate(); - } - - /** - * Page the terminal view horizontally. - * - * @param deltaColumns the number of columns to scroll. Positive scrolls to - * the right. - */ - public void pageHorizontal(int deltaColumns) { - mLeftColumn = - Math.max(0, Math.min(mLeftColumn + deltaColumns, mColumns - - mVisibleColumns)); - invalidate(); - } - - /** - * Sets the text size, which in turn sets the number of rows and columns - * - * @param fontSize the new font size, in pixels. - */ - public void setTextSize(int fontSize) { - mTextSize = fontSize; - updateText(); - } - - public void setCursorStyle(int style, int blink) { - mCursorStyle = style; - if (blink != 0 && mCursorBlink == 0) { - mHandler.postDelayed(mBlinkCursor, CURSOR_BLINK_PERIOD); - } else if (blink == 0 && mCursorBlink != 0) { - mHandler.removeCallbacks(mBlinkCursor); - } - mCursorBlink = blink; - } - - public void setUseCookedIME(boolean useCookedIME) { - mUseCookedIme = useCookedIME; - } - - // Begin GestureDetector.OnGestureListener methods - - public boolean onSingleTapUp(MotionEvent e) { - return true; - } - - public void onLongPress(MotionEvent e) { - showContextMenu(); - } - - public boolean onScroll(MotionEvent e1, MotionEvent e2, - float distanceX, float distanceY) { - distanceY += mScrollRemainder; - int deltaRows = (int) (distanceY / mCharacterHeight); - mScrollRemainder = distanceY - deltaRows * mCharacterHeight; - mTopRow = - Math.min(0, Math.max(-(mTranscriptScreen - .getActiveTranscriptRows()), mTopRow + deltaRows)); - invalidate(); - - return true; - } - - public void onSingleTapConfirmed(MotionEvent e) { - } - - public boolean onJumpTapDown(MotionEvent e1, MotionEvent e2) { - // Scroll to bottom - mTopRow = 0; - invalidate(); - return true; - } - - public boolean onJumpTapUp(MotionEvent e1, MotionEvent e2) { - // Scroll to top - mTopRow = -mTranscriptScreen.getActiveTranscriptRows(); - invalidate(); - return true; - } - - public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, - float velocityY) { - // TODO: add animation man's (non animated) fling - mScrollRemainder = 0.0f; - onScroll(e1, e2, 2 * velocityX, -2 * velocityY); - return true; - } - - public void onShowPress(MotionEvent e) { - } - - public boolean onDown(MotionEvent e) { - mScrollRemainder = 0.0f; - return true; - } - - // End GestureDetector.OnGestureListener methods - - @Override public boolean onTouchEvent(MotionEvent ev) { - if (mIsSelectingText) { - return onTouchEventWhileSelectingText(ev); - } else { - return mGestureDetector.onTouchEvent(ev); - } - } - - private boolean onTouchEventWhileSelectingText(MotionEvent ev) { - int action = ev.getAction(); - int cx = (int)(ev.getX() / mCharacterWidth); - int cy = Math.max(0, - (int)((ev.getY() + SELECT_TEXT_OFFSET_Y * mScaledDensity) - / mCharacterHeight) + mTopRow); - switch (action) { - case MotionEvent.ACTION_DOWN: - mSelXAnchor = cx; - mSelYAnchor = cy; - mSelX1 = cx; - mSelY1 = cy; - mSelX2 = mSelX1; - mSelY2 = mSelY1; - break; - case MotionEvent.ACTION_MOVE: - case MotionEvent.ACTION_UP: - int minx = Math.min(mSelXAnchor, cx); - int maxx = Math.max(mSelXAnchor, cx); - int miny = Math.min(mSelYAnchor, cy); - int maxy = Math.max(mSelYAnchor, cy); - mSelX1 = minx; - mSelY1 = miny; - mSelX2 = maxx; - mSelY2 = maxy; - if (action == MotionEvent.ACTION_UP) { - ClipboardManager clip = (ClipboardManager) - getContext().getApplicationContext() - .getSystemService(Context.CLIPBOARD_SERVICE); - clip.setText(getSelectedText().trim()); - toggleSelectingText(); - } - invalidate(); - break; - default: - toggleSelectingText(); - invalidate(); - break; - } - return true; - } - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - if (LOG_KEY_EVENTS) { - Log.w(TAG, "onKeyDown " + keyCode); - } - if (handleControlKey(keyCode, true)) { - return true; - } else if (handleFnKey(keyCode, true)) { - return true; - } else if (isSystemKey(keyCode, event)) { - // Don't intercept the system keys - return super.onKeyDown(keyCode, event); - } - - // Translate the keyCode into an ASCII character. - - try { - mKeyListener.keyDown(keyCode, event, mTermOut, - getKeypadApplicationMode()); - } catch (IOException e) { - // Ignore I/O exceptions - } - return true; - } - - @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - if (LOG_KEY_EVENTS) { - Log.w(TAG, "onKeyUp " + keyCode); - } - if (handleControlKey(keyCode, false)) { - return true; - } else if (handleFnKey(keyCode, false)) { - return true; - } else if (isSystemKey(keyCode, event)) { - // Don't intercept the system keys - return super.onKeyUp(keyCode, event); - } - - mKeyListener.keyUp(keyCode); - return true; - } - - - private boolean handleControlKey(int keyCode, boolean down) { - if (keyCode == mTerm.getControlKeyCode()) { - if (LOG_KEY_EVENTS) { - Log.w(TAG, "handleControlKey " + keyCode); - } - mKeyListener.handleControlKey(down); - return true; - } - return false; - } - - private boolean handleFnKey(int keyCode, boolean down) { - if (keyCode == mTerm.getFnKeyCode()) { - if (LOG_KEY_EVENTS) { - Log.w(TAG, "handleFnKey " + keyCode); - } - mKeyListener.handleFnKey(down); - return true; - } - return false; - } - - private boolean isSystemKey(int keyCode, KeyEvent event) { - return event.isSystem(); - } - - private void updateText() { - if (mTextSize > 0) { - mTextRenderer = new PaintRenderer(mTextSize, mForeground, - mBackground); - } - else { - mTextRenderer = new Bitmap4x8FontRenderer(getResources(), - mForeground, mBackground); - } - mBackgroundPaint.setColor(mBackground); - mCharacterWidth = mTextRenderer.getCharacterWidth(); - mCharacterHeight = mTextRenderer.getCharacterHeight(); - - updateSize(true); - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - boolean oldKnownSize = mKnownSize; - if (!mKnownSize) { - mKnownSize = true; - } - updateSize(false); - if (!oldKnownSize) { - // Set up a thread to read input from the - // pseudo-teletype: - - mPollingThread = new Thread(new Runnable() { - - public void run() { - try { - while(true) { - int read = mTermIn.read(mBuffer); - mByteQueue.write(mBuffer, 0, read); - mHandler.sendMessage( - mHandler.obtainMessage(UPDATE)); - } - } catch (IOException e) { - } catch (InterruptedException e) { - } - } - private byte[] mBuffer = new byte[4096]; - }); - mPollingThread.setName("Input reader"); - mPollingThread.start(); - } - } - - private void updateSize(int w, int h) { - mColumns = Math.max(1, w / mCharacterWidth); - mRows = Math.max(1, h / mCharacterHeight); - mVisibleColumns = mVisibleWidth / mCharacterWidth; - - // Inform the attached pty of our new size: - Exec.setPtyWindowSize(mTermFd, mRows, mColumns, w, h); - - - if (mTranscriptScreen != null) { - mEmulator.updateSize(mColumns, mRows); - } else { - mTranscriptScreen = - new TranscriptScreen(mColumns, TRANSCRIPT_ROWS, mRows, 0, 7); - mEmulator = - new TerminalEmulator(mTranscriptScreen, mColumns, mRows, - mTermOut); - } - - // Reset our paging: - mTopRow = 0; - mLeftColumn = 0; - - invalidate(); - } - - void updateSize(boolean force) { - if (mKnownSize) { - getWindowVisibleDisplayFrame(mVisibleRect); - int w = mVisibleRect.width(); - int h = mVisibleRect.height(); - // Log.w("Term", "(" + w + ", " + h + ")"); - if (force || w != mVisibleWidth || h != mVisibleHeight) { - mVisibleWidth = w; - mVisibleHeight = h; - updateSize(mVisibleWidth, mVisibleHeight); - } - } - } - - /** - * Look for new input from the ptty, send it to the terminal emulator. - */ - private void update() { - int bytesAvailable = mByteQueue.getBytesAvailable(); - int bytesToRead = Math.min(bytesAvailable, mReceiveBuffer.length); - try { - int bytesRead = mByteQueue.read(mReceiveBuffer, 0, bytesToRead); - append(mReceiveBuffer, 0, bytesRead); - } catch (InterruptedException e) { - } - } - - @Override - protected void onDraw(Canvas canvas) { - updateSize(false); - int w = getWidth(); - int h = getHeight(); - canvas.drawRect(0, 0, w, h, mBackgroundPaint); - float x = -mLeftColumn * mCharacterWidth; - float y = mCharacterHeight; - int endLine = mTopRow + mRows; - int cx = mEmulator.getCursorCol(); - int cy = mEmulator.getCursorRow(); - for (int i = mTopRow; i < endLine; i++) { - int cursorX = -1; - if (i == cy && mCursorVisible) { - cursorX = cx; - } - int selx1 = -1; - int selx2 = -1; - if ( i >= mSelY1 && i <= mSelY2 ) { - if ( i == mSelY1 ) { - selx1 = mSelX1; - } - if ( i == mSelY2 ) { - selx2 = mSelX2; - } else { - selx2 = mColumns; - } - } - mTranscriptScreen.drawText(i, canvas, x, y, mTextRenderer, cursorX, selx1, selx2, mImeBuffer); - y += mCharacterHeight; - } - } - - private void ensureCursorVisible() { - mTopRow = 0; - if (mVisibleColumns > 0) { - int cx = mEmulator.getCursorCol(); - int visibleCursorX = mEmulator.getCursorCol() - mLeftColumn; - if (visibleCursorX < 0) { - mLeftColumn = cx; - } else if (visibleCursorX >= mVisibleColumns) { - mLeftColumn = (cx - mVisibleColumns) + 1; - } - } - } - - public void toggleSelectingText() { - mIsSelectingText = ! mIsSelectingText; - setVerticalScrollBarEnabled( ! mIsSelectingText ); - if ( ! mIsSelectingText ) { - mSelX1 = -1; - mSelY1 = -1; - mSelX2 = -1; - mSelY2 = -1; - } - } - - public boolean getSelectingText() { - return mIsSelectingText; - } - - public String getSelectedText() { - return mEmulator.getSelectedText(mSelX1, mSelY1, mSelX2, mSelY2); - } -} - - -/** - * An ASCII key listener. Supports control characters and escape. Keeps track of - * the current state of the alt, shift, and control keys. - */ -class TermKeyListener { - /** - * Android key codes that are defined in the Android 2.3 API. - * We want to recognize these codes, because they will be sent to our - * app when we run on Android 2.3 systems. - * But we don't want to accidentally use 2.3-specific APIs. - * So we compile against the Android 1.6 APIs, and have a copy of the codes here. - */ - - /** Key code constant: Unknown key code. */ - public static final int KEYCODE_UNKNOWN = 0; - /** Key code constant: Soft Left key. - * Usually situated below the display on phones and used as a multi-function - * feature key for selecting a software defined function shown on the bottom left - * of the display. */ - public static final int KEYCODE_SOFT_LEFT = 1; - /** Key code constant: Soft Right key. - * Usually situated below the display on phones and used as a multi-function - * feature key for selecting a software defined function shown on the bottom right - * of the display. */ - public static final int KEYCODE_SOFT_RIGHT = 2; - /** Key code constant: Home key. - * This key is handled by the framework and is never delivered to applications. */ - public static final int KEYCODE_HOME = 3; - /** Key code constant: Back key. */ - public static final int KEYCODE_BACK = 4; - /** Key code constant: Call key. */ - public static final int KEYCODE_CALL = 5; - /** Key code constant: End Call key. */ - public static final int KEYCODE_ENDCALL = 6; - /** Key code constant: '0' key. */ - public static final int KEYCODE_0 = 7; - /** Key code constant: '1' key. */ - public static final int KEYCODE_1 = 8; - /** Key code constant: '2' key. */ - public static final int KEYCODE_2 = 9; - /** Key code constant: '3' key. */ - public static final int KEYCODE_3 = 10; - /** Key code constant: '4' key. */ - public static final int KEYCODE_4 = 11; - /** Key code constant: '5' key. */ - public static final int KEYCODE_5 = 12; - /** Key code constant: '6' key. */ - public static final int KEYCODE_6 = 13; - /** Key code constant: '7' key. */ - public static final int KEYCODE_7 = 14; - /** Key code constant: '8' key. */ - public static final int KEYCODE_8 = 15; - /** Key code constant: '9' key. */ - public static final int KEYCODE_9 = 16; - /** Key code constant: '*' key. */ - public static final int KEYCODE_STAR = 17; - /** Key code constant: '#' key. */ - public static final int KEYCODE_POUND = 18; - /** Key code constant: Directional Pad Up key. - * May also be synthesized from trackball motions. */ - public static final int KEYCODE_DPAD_UP = 19; - /** Key code constant: Directional Pad Down key. - * May also be synthesized from trackball motions. */ - public static final int KEYCODE_DPAD_DOWN = 20; - /** Key code constant: Directional Pad Left key. - * May also be synthesized from trackball motions. */ - public static final int KEYCODE_DPAD_LEFT = 21; - /** Key code constant: Directional Pad Right key. - * May also be synthesized from trackball motions. */ - public static final int KEYCODE_DPAD_RIGHT = 22; - /** Key code constant: Directional Pad Center key. - * May also be synthesized from trackball motions. */ - public static final int KEYCODE_DPAD_CENTER = 23; - /** Key code constant: Volume Up key. - * Adjusts the speaker volume up. */ - public static final int KEYCODE_VOLUME_UP = 24; - /** Key code constant: Volume Down key. - * Adjusts the speaker volume down. */ - public static final int KEYCODE_VOLUME_DOWN = 25; - /** Key code constant: Power key. */ - public static final int KEYCODE_POWER = 26; - /** Key code constant: Camera key. - * Used to launch a camera application or take pictures. */ - public static final int KEYCODE_CAMERA = 27; - /** Key code constant: Clear key. */ - public static final int KEYCODE_CLEAR = 28; - /** Key code constant: 'A' key. */ - public static final int KEYCODE_A = 29; - /** Key code constant: 'B' key. */ - public static final int KEYCODE_B = 30; - /** Key code constant: 'C' key. */ - public static final int KEYCODE_C = 31; - /** Key code constant: 'D' key. */ - public static final int KEYCODE_D = 32; - /** Key code constant: 'E' key. */ - public static final int KEYCODE_E = 33; - /** Key code constant: 'F' key. */ - public static final int KEYCODE_F = 34; - /** Key code constant: 'G' key. */ - public static final int KEYCODE_G = 35; - /** Key code constant: 'H' key. */ - public static final int KEYCODE_H = 36; - /** Key code constant: 'I' key. */ - public static final int KEYCODE_I = 37; - /** Key code constant: 'J' key. */ - public static final int KEYCODE_J = 38; - /** Key code constant: 'K' key. */ - public static final int KEYCODE_K = 39; - /** Key code constant: 'L' key. */ - public static final int KEYCODE_L = 40; - /** Key code constant: 'M' key. */ - public static final int KEYCODE_M = 41; - /** Key code constant: 'N' key. */ - public static final int KEYCODE_N = 42; - /** Key code constant: 'O' key. */ - public static final int KEYCODE_O = 43; - /** Key code constant: 'P' key. */ - public static final int KEYCODE_P = 44; - /** Key code constant: 'Q' key. */ - public static final int KEYCODE_Q = 45; - /** Key code constant: 'R' key. */ - public static final int KEYCODE_R = 46; - /** Key code constant: 'S' key. */ - public static final int KEYCODE_S = 47; - /** Key code constant: 'T' key. */ - public static final int KEYCODE_T = 48; - /** Key code constant: 'U' key. */ - public static final int KEYCODE_U = 49; - /** Key code constant: 'V' key. */ - public static final int KEYCODE_V = 50; - /** Key code constant: 'W' key. */ - public static final int KEYCODE_W = 51; - /** Key code constant: 'X' key. */ - public static final int KEYCODE_X = 52; - /** Key code constant: 'Y' key. */ - public static final int KEYCODE_Y = 53; - /** Key code constant: 'Z' key. */ - public static final int KEYCODE_Z = 54; - /** Key code constant: ',' key. */ - public static final int KEYCODE_COMMA = 55; - /** Key code constant: '.' key. */ - public static final int KEYCODE_PERIOD = 56; - /** Key code constant: Left Alt modifier key. */ - public static final int KEYCODE_ALT_LEFT = 57; - /** Key code constant: Right Alt modifier key. */ - public static final int KEYCODE_ALT_RIGHT = 58; - /** Key code constant: Left Shift modifier key. */ - public static final int KEYCODE_SHIFT_LEFT = 59; - /** Key code constant: Right Shift modifier key. */ - public static final int KEYCODE_SHIFT_RIGHT = 60; - /** Key code constant: Tab key. */ - public static final int KEYCODE_TAB = 61; - /** Key code constant: Space key. */ - public static final int KEYCODE_SPACE = 62; - /** Key code constant: Symbol modifier key. - * Used to enter alternate symbols. */ - public static final int KEYCODE_SYM = 63; - /** Key code constant: Explorer special function key. - * Used to launch a browser application. */ - public static final int KEYCODE_EXPLORER = 64; - /** Key code constant: Envelope special function key. - * Used to launch a mail application. */ - public static final int KEYCODE_ENVELOPE = 65; - /** Key code constant: Enter key. */ - public static final int KEYCODE_ENTER = 66; - /** Key code constant: Backspace key. - * Deletes characters before the insertion point, unlike {@link #KEYCODE_FORWARD_DEL}. */ - public static final int KEYCODE_DEL = 67; - /** Key code constant: '`' (backtick) key. */ - public static final int KEYCODE_GRAVE = 68; - /** Key code constant: '-'. */ - public static final int KEYCODE_MINUS = 69; - /** Key code constant: '=' key. */ - public static final int KEYCODE_EQUALS = 70; - /** Key code constant: '[' key. */ - public static final int KEYCODE_LEFT_BRACKET = 71; - /** Key code constant: ']' key. */ - public static final int KEYCODE_RIGHT_BRACKET = 72; - /** Key code constant: '\' key. */ - public static final int KEYCODE_BACKSLASH = 73; - /** Key code constant: ';' key. */ - public static final int KEYCODE_SEMICOLON = 74; - /** Key code constant: ''' (apostrophe) key. */ - public static final int KEYCODE_APOSTROPHE = 75; - /** Key code constant: '/' key. */ - public static final int KEYCODE_SLASH = 76; - /** Key code constant: '@' key. */ - public static final int KEYCODE_AT = 77; - /** Key code constant: Number modifier key. - * Used to enter numeric symbols. - * This key is not Num Lock; it is more like {@link #KEYCODE_ALT_LEFT} and is - * interpreted as an ALT key by {@link android.text.method.MetaKeyKeyListener}. */ - public static final int KEYCODE_NUM = 78; - /** Key code constant: Headset Hook key. - * Used to hang up calls and stop media. */ - public static final int KEYCODE_HEADSETHOOK = 79; - /** Key code constant: Camera Focus key. - * Used to focus the camera. */ - public static final int KEYCODE_FOCUS = 80; // *Camera* focus - /** Key code constant: '+' key. */ - public static final int KEYCODE_PLUS = 81; - /** Key code constant: Menu key. */ - public static final int KEYCODE_MENU = 82; - /** Key code constant: Notification key. */ - public static final int KEYCODE_NOTIFICATION = 83; - /** Key code constant: Search key. */ - public static final int KEYCODE_SEARCH = 84; - /** Key code constant: Play/Pause media key. */ - public static final int KEYCODE_MEDIA_PLAY_PAUSE= 85; - /** Key code constant: Stop media key. */ - public static final int KEYCODE_MEDIA_STOP = 86; - /** Key code constant: Play Next media key. */ - public static final int KEYCODE_MEDIA_NEXT = 87; - /** Key code constant: Play Previous media key. */ - public static final int KEYCODE_MEDIA_PREVIOUS = 88; - /** Key code constant: Rewind media key. */ - public static final int KEYCODE_MEDIA_REWIND = 89; - /** Key code constant: Fast Forward media key. */ - public static final int KEYCODE_MEDIA_FAST_FORWARD = 90; - /** Key code constant: Mute key. - * Mutes the microphone, unlike {@link #KEYCODE_VOLUME_MUTE}. */ - public static final int KEYCODE_MUTE = 91; - /** Key code constant: Page Up key. */ - public static final int KEYCODE_PAGE_UP = 92; - /** Key code constant: Page Down key. */ - public static final int KEYCODE_PAGE_DOWN = 93; - /** Key code constant: Picture Symbols modifier key. - * Used to switch symbol sets (Emoji, Kao-moji). */ - public static final int KEYCODE_PICTSYMBOLS = 94; // switch symbol-sets (Emoji,Kao-moji) - /** Key code constant: Switch Charset modifier key. - * Used to switch character sets (Kanji, Katakana). */ - public static final int KEYCODE_SWITCH_CHARSET = 95; // switch char-sets (Kanji,Katakana) - /** Key code constant: A Button key. - * On a game controller, the A button should be either the button labeled A - * or the first button on the upper row of controller buttons. */ - public static final int KEYCODE_BUTTON_A = 96; - /** Key code constant: B Button key. - * On a game controller, the B button should be either the button labeled B - * or the second button on the upper row of controller buttons. */ - public static final int KEYCODE_BUTTON_B = 97; - /** Key code constant: C Button key. - * On a game controller, the C button should be either the button labeled C - * or the third button on the upper row of controller buttons. */ - public static final int KEYCODE_BUTTON_C = 98; - /** Key code constant: X Button key. - * On a game controller, the X button should be either the button labeled X - * or the first button on the lower row of controller buttons. */ - public static final int KEYCODE_BUTTON_X = 99; - /** Key code constant: Y Button key. - * On a game controller, the Y button should be either the button labeled Y - * or the second button on the lower row of controller buttons. */ - public static final int KEYCODE_BUTTON_Y = 100; - /** Key code constant: Z Button key. - * On a game controller, the Z button should be either the button labeled Z - * or the third button on the lower row of controller buttons. */ - public static final int KEYCODE_BUTTON_Z = 101; - /** Key code constant: L1 Button key. - * On a game controller, the L1 button should be either the button labeled L1 (or L) - * or the top left trigger button. */ - public static final int KEYCODE_BUTTON_L1 = 102; - /** Key code constant: R1 Button key. - * On a game controller, the R1 button should be either the button labeled R1 (or R) - * or the top right trigger button. */ - public static final int KEYCODE_BUTTON_R1 = 103; - /** Key code constant: L2 Button key. - * On a game controller, the L2 button should be either the button labeled L2 - * or the bottom left trigger button. */ - public static final int KEYCODE_BUTTON_L2 = 104; - /** Key code constant: R2 Button key. - * On a game controller, the R2 button should be either the button labeled R2 - * or the bottom right trigger button. */ - public static final int KEYCODE_BUTTON_R2 = 105; - /** Key code constant: Left Thumb Button key. - * On a game controller, the left thumb button indicates that the left (or only) - * joystick is pressed. */ - public static final int KEYCODE_BUTTON_THUMBL = 106; - /** Key code constant: Right Thumb Button key. - * On a game controller, the right thumb button indicates that the right - * joystick is pressed. */ - public static final int KEYCODE_BUTTON_THUMBR = 107; - /** Key code constant: Start Button key. - * On a game controller, the button labeled Start. */ - public static final int KEYCODE_BUTTON_START = 108; - /** Key code constant: Select Button key. - * On a game controller, the button labeled Select. */ - public static final int KEYCODE_BUTTON_SELECT = 109; - /** Key code constant: Mode Button key. - * On a game controller, the button labeled Mode. */ - public static final int KEYCODE_BUTTON_MODE = 110; - /** Key code constant: Escape key. */ - public static final int KEYCODE_ESCAPE = 111; - /** Key code constant: Forward Delete key. - * Deletes characters ahead of the insertion point, unlike {@link #KEYCODE_DEL}. */ - public static final int KEYCODE_FORWARD_DEL = 112; - /** Key code constant: Left Control modifier key. */ - public static final int KEYCODE_CTRL_LEFT = 113; - /** Key code constant: Right Control modifier key. */ - public static final int KEYCODE_CTRL_RIGHT = 114; - /** Key code constant: Caps Lock modifier key. */ - public static final int KEYCODE_CAPS_LOCK = 115; - /** Key code constant: Scroll Lock key. */ - public static final int KEYCODE_SCROLL_LOCK = 116; - /** Key code constant: Left Meta modifier key. */ - public static final int KEYCODE_META_LEFT = 117; - /** Key code constant: Right Meta modifier key. */ - public static final int KEYCODE_META_RIGHT = 118; - /** Key code constant: Function modifier key. */ - public static final int KEYCODE_FUNCTION = 119; - /** Key code constant: System Request / Print Screen key. */ - public static final int KEYCODE_SYSRQ = 120; - /** Key code constant: Break / Pause key. */ - public static final int KEYCODE_BREAK = 121; - /** Key code constant: Home Movement key. - * Used for scrolling or moving the cursor around to the start of a line - * or to the top of a list. */ - public static final int KEYCODE_MOVE_HOME = 122; - /** Key code constant: End Movement key. - * Used for scrolling or moving the cursor around to the end of a line - * or to the bottom of a list. */ - public static final int KEYCODE_MOVE_END = 123; - /** Key code constant: Insert key. - * Toggles insert / overwrite edit mode. */ - public static final int KEYCODE_INSERT = 124; - /** Key code constant: Forward key. - * Navigates forward in the history stack. Complement of {@link #KEYCODE_BACK}. */ - public static final int KEYCODE_FORWARD = 125; - /** Key code constant: Play media key. */ - public static final int KEYCODE_MEDIA_PLAY = 126; - /** Key code constant: Pause media key. */ - public static final int KEYCODE_MEDIA_PAUSE = 127; - /** Key code constant: Close media key. - * May be used to close a CD tray, for example. */ - public static final int KEYCODE_MEDIA_CLOSE = 128; - /** Key code constant: Eject media key. - * May be used to eject a CD tray, for example. */ - public static final int KEYCODE_MEDIA_EJECT = 129; - /** Key code constant: Record media key. */ - public static final int KEYCODE_MEDIA_RECORD = 130; - /** Key code constant: F1 key. */ - public static final int KEYCODE_F1 = 131; - /** Key code constant: F2 key. */ - public static final int KEYCODE_F2 = 132; - /** Key code constant: F3 key. */ - public static final int KEYCODE_F3 = 133; - /** Key code constant: F4 key. */ - public static final int KEYCODE_F4 = 134; - /** Key code constant: F5 key. */ - public static final int KEYCODE_F5 = 135; - /** Key code constant: F6 key. */ - public static final int KEYCODE_F6 = 136; - /** Key code constant: F7 key. */ - public static final int KEYCODE_F7 = 137; - /** Key code constant: F8 key. */ - public static final int KEYCODE_F8 = 138; - /** Key code constant: F9 key. */ - public static final int KEYCODE_F9 = 139; - /** Key code constant: F10 key. */ - public static final int KEYCODE_F10 = 140; - /** Key code constant: F11 key. */ - public static final int KEYCODE_F11 = 141; - /** Key code constant: F12 key. */ - public static final int KEYCODE_F12 = 142; - /** Key code constant: Num Lock modifier key. - * This is the Num Lock key; it is different from {@link #KEYCODE_NUM}. - * This key generally modifies the behavior of other keys on the numeric keypad. */ - public static final int KEYCODE_NUM_LOCK = 143; - /** Key code constant: Numeric keypad '0' key. */ - public static final int KEYCODE_NUMPAD_0 = 144; - /** Key code constant: Numeric keypad '1' key. */ - public static final int KEYCODE_NUMPAD_1 = 145; - /** Key code constant: Numeric keypad '2' key. */ - public static final int KEYCODE_NUMPAD_2 = 146; - /** Key code constant: Numeric keypad '3' key. */ - public static final int KEYCODE_NUMPAD_3 = 147; - /** Key code constant: Numeric keypad '4' key. */ - public static final int KEYCODE_NUMPAD_4 = 148; - /** Key code constant: Numeric keypad '5' key. */ - public static final int KEYCODE_NUMPAD_5 = 149; - /** Key code constant: Numeric keypad '6' key. */ - public static final int KEYCODE_NUMPAD_6 = 150; - /** Key code constant: Numeric keypad '7' key. */ - public static final int KEYCODE_NUMPAD_7 = 151; - /** Key code constant: Numeric keypad '8' key. */ - public static final int KEYCODE_NUMPAD_8 = 152; - /** Key code constant: Numeric keypad '9' key. */ - public static final int KEYCODE_NUMPAD_9 = 153; - /** Key code constant: Numeric keypad '/' key (for division). */ - public static final int KEYCODE_NUMPAD_DIVIDE = 154; - /** Key code constant: Numeric keypad '*' key (for multiplication). */ - public static final int KEYCODE_NUMPAD_MULTIPLY = 155; - /** Key code constant: Numeric keypad '-' key (for subtraction). */ - public static final int KEYCODE_NUMPAD_SUBTRACT = 156; - /** Key code constant: Numeric keypad '+' key (for addition). */ - public static final int KEYCODE_NUMPAD_ADD = 157; - /** Key code constant: Numeric keypad '.' key (for decimals or digit grouping). */ - public static final int KEYCODE_NUMPAD_DOT = 158; - /** Key code constant: Numeric keypad ',' key (for decimals or digit grouping). */ - public static final int KEYCODE_NUMPAD_COMMA = 159; - /** Key code constant: Numeric keypad Enter key. */ - public static final int KEYCODE_NUMPAD_ENTER = 160; - /** Key code constant: Numeric keypad '=' key. */ - public static final int KEYCODE_NUMPAD_EQUALS = 161; - /** Key code constant: Numeric keypad '(' key. */ - public static final int KEYCODE_NUMPAD_LEFT_PAREN = 162; - /** Key code constant: Numeric keypad ')' key. */ - public static final int KEYCODE_NUMPAD_RIGHT_PAREN = 163; - /** Key code constant: Volume Mute key. - * Mutes the speaker, unlike {@link #KEYCODE_MUTE}. - * This key should normally be implemented as a toggle such that the first press - * mutes the speaker and the second press restores the original volume. */ - public static final int KEYCODE_VOLUME_MUTE = 164; - /** Key code constant: Info key. - * Common on TV remotes to show additional information related to what is - * currently being viewed. */ - public static final int KEYCODE_INFO = 165; - /** Key code constant: Channel up key. - * On TV remotes, increments the television channel. */ - public static final int KEYCODE_CHANNEL_UP = 166; - /** Key code constant: Channel down key. - * On TV remotes, decrements the television channel. */ - public static final int KEYCODE_CHANNEL_DOWN = 167; - /** Key code constant: Zoom in key. */ - public static final int KEYCODE_ZOOM_IN = 168; - /** Key code constant: Zoom out key. */ - public static final int KEYCODE_ZOOM_OUT = 169; - /** Key code constant: TV key. - * On TV remotes, switches to viewing live TV. */ - public static final int KEYCODE_TV = 170; - /** Key code constant: Window key. - * On TV remotes, toggles picture-in-picture mode or other windowing functions. */ - public static final int KEYCODE_WINDOW = 171; - /** Key code constant: Guide key. - * On TV remotes, shows a programming guide. */ - public static final int KEYCODE_GUIDE = 172; - /** Key code constant: DVR key. - * On some TV remotes, switches to a DVR mode for recorded shows. */ - public static final int KEYCODE_DVR = 173; - /** Key code constant: Bookmark key. - * On some TV remotes, bookmarks content or web pages. */ - public static final int KEYCODE_BOOKMARK = 174; - /** Key code constant: Toggle captions key. - * Switches the mode for closed-captioning text, for example during television shows. */ - public static final int KEYCODE_CAPTIONS = 175; - /** Key code constant: Settings key. - * Starts the system settings activity. */ - public static final int KEYCODE_SETTINGS = 176; - /** Key code constant: TV power key. - * On TV remotes, toggles the power on a television screen. */ - public static final int KEYCODE_TV_POWER = 177; - /** Key code constant: TV input key. - * On TV remotes, switches the input on a television screen. */ - public static final int KEYCODE_TV_INPUT = 178; - /** Key code constant: Set-top-box power key. - * On TV remotes, toggles the power on an external Set-top-box. */ - public static final int KEYCODE_STB_POWER = 179; - /** Key code constant: Set-top-box input key. - * On TV remotes, switches the input mode on an external Set-top-box. */ - public static final int KEYCODE_STB_INPUT = 180; - /** Key code constant: A/V Receiver power key. - * On TV remotes, toggles the power on an external A/V Receiver. */ - public static final int KEYCODE_AVR_POWER = 181; - /** Key code constant: A/V Receiver input key. - * On TV remotes, switches the input mode on an external A/V Receiver. */ - public static final int KEYCODE_AVR_INPUT = 182; - /** Key code constant: Red "programmable" key. - * On TV remotes, acts as a contextual/programmable key. */ - public static final int KEYCODE_PROG_RED = 183; - /** Key code constant: Green "programmable" key. - * On TV remotes, actsas a contextual/programmable key. */ - public static final int KEYCODE_PROG_GREEN = 184; - /** Key code constant: Yellow "programmable" key. - * On TV remotes, acts as a contextual/programmable key. */ - public static final int KEYCODE_PROG_YELLOW = 185; - /** Key code constant: Blue "programmable" key. - * On TV remotes, acts as a contextual/programmable key. */ - public static final int KEYCODE_PROG_BLUE = 186; - - private static final int LAST_KEYCODE = KEYCODE_PROG_BLUE; - - private String[] mKeyCodes = new String[256]; - private String[] mAppKeyCodes = new String[256]; - - private void initKeyCodes() { - mKeyCodes[KEYCODE_DPAD_CENTER] = "\015"; - mKeyCodes[KEYCODE_DPAD_UP] = "\033[A"; - mKeyCodes[KEYCODE_DPAD_DOWN] = "\033[B"; - mKeyCodes[KEYCODE_DPAD_RIGHT] = "\033[C"; - mKeyCodes[KEYCODE_DPAD_LEFT] = "\033[D"; - mKeyCodes[KEYCODE_F1] = "\033[OP"; - mKeyCodes[KEYCODE_F2] = "\033[OQ"; - mKeyCodes[KEYCODE_F3] = "\033[OR"; - mKeyCodes[KEYCODE_F4] = "\033[OS"; - mKeyCodes[KEYCODE_F5] = "\033[15~"; - mKeyCodes[KEYCODE_F6] = "\033[17~"; - mKeyCodes[KEYCODE_F7] = "\033[18~"; - mKeyCodes[KEYCODE_F8] = "\033[19~"; - mKeyCodes[KEYCODE_F9] = "\033[20~"; - mKeyCodes[KEYCODE_F10] = "\033[21~"; - mKeyCodes[KEYCODE_F11] = "\033[23~"; - mKeyCodes[KEYCODE_F12] = "\033[24~"; - mKeyCodes[KEYCODE_SYSRQ] = "\033[32~"; // Sys Request / Print - // Is this Scroll lock? mKeyCodes[Cancel] = "\033[33~"; - mKeyCodes[KEYCODE_BREAK] = "\033[34~"; // Pause/Break - - mKeyCodes[KEYCODE_TAB] = "\011"; - mKeyCodes[KEYCODE_ENTER] = "\015"; - mKeyCodes[KEYCODE_ESCAPE] = "\033"; - - mKeyCodes[KEYCODE_INSERT] = "\033[2~"; - mKeyCodes[KEYCODE_FORWARD_DEL] = "\033[3~"; - mKeyCodes[KEYCODE_MOVE_HOME] = "\033[1~"; - mKeyCodes[KEYCODE_MOVE_END] = "\033[4~"; - mKeyCodes[KEYCODE_PAGE_UP] = "\033[5~"; - mKeyCodes[KEYCODE_PAGE_DOWN] = "\033[6~"; - mKeyCodes[KEYCODE_DEL]= "\177"; - mKeyCodes[KEYCODE_NUM_LOCK] = "\033OP"; - mKeyCodes[KEYCODE_NUMPAD_DIVIDE] = "/"; - mKeyCodes[KEYCODE_NUMPAD_MULTIPLY] = "*"; - mKeyCodes[KEYCODE_NUMPAD_SUBTRACT] = "-"; - mKeyCodes[KEYCODE_NUMPAD_ADD] = "+"; - mKeyCodes[KEYCODE_NUMPAD_ENTER] = "\015"; - mKeyCodes[KEYCODE_NUMPAD_EQUALS] = "="; - mKeyCodes[KEYCODE_NUMPAD_DOT] = "."; - mKeyCodes[KEYCODE_NUMPAD_COMMA] = ","; - mKeyCodes[KEYCODE_NUMPAD_0] = "0"; - mKeyCodes[KEYCODE_NUMPAD_1] = "1"; - mKeyCodes[KEYCODE_NUMPAD_2] = "2"; - mKeyCodes[KEYCODE_NUMPAD_3] = "3"; - mKeyCodes[KEYCODE_NUMPAD_4] = "4"; - mKeyCodes[KEYCODE_NUMPAD_5] = "5"; - mKeyCodes[KEYCODE_NUMPAD_6] = "6"; - mKeyCodes[KEYCODE_NUMPAD_7] = "7"; - mKeyCodes[KEYCODE_NUMPAD_8] = "8"; - mKeyCodes[KEYCODE_NUMPAD_9] = "9"; - - mAppKeyCodes[KEYCODE_DPAD_UP] = "\033OA"; - mAppKeyCodes[KEYCODE_DPAD_DOWN] = "\033OB"; - mAppKeyCodes[KEYCODE_DPAD_RIGHT] = "\033OC"; - mAppKeyCodes[KEYCODE_DPAD_LEFT] = "\033OD"; - mAppKeyCodes[KEYCODE_NUMPAD_DIVIDE] = "\033Oo"; - mAppKeyCodes[KEYCODE_NUMPAD_MULTIPLY] = "\033Oj"; - mAppKeyCodes[KEYCODE_NUMPAD_SUBTRACT] = "\033Om"; - mAppKeyCodes[KEYCODE_NUMPAD_ADD] = "\033Ok"; - mAppKeyCodes[KEYCODE_NUMPAD_ENTER] = "\033OM"; - mAppKeyCodes[KEYCODE_NUMPAD_EQUALS] = "\033OX"; - mAppKeyCodes[KEYCODE_NUMPAD_DOT] = "\033On"; - mAppKeyCodes[KEYCODE_NUMPAD_COMMA] = "\033Ol"; - mAppKeyCodes[KEYCODE_NUMPAD_0] = "\033Op"; - mAppKeyCodes[KEYCODE_NUMPAD_1] = "\033Oq"; - mAppKeyCodes[KEYCODE_NUMPAD_2] = "\033Or"; - mAppKeyCodes[KEYCODE_NUMPAD_3] = "\033Os"; - mAppKeyCodes[KEYCODE_NUMPAD_4] = "\033Ot"; - mAppKeyCodes[KEYCODE_NUMPAD_5] = "\033Ou"; - mAppKeyCodes[KEYCODE_NUMPAD_6] = "\033Ov"; - mAppKeyCodes[KEYCODE_NUMPAD_7] = "\033Ow"; - mAppKeyCodes[KEYCODE_NUMPAD_8] = "\033Ox"; - mAppKeyCodes[KEYCODE_NUMPAD_9] = "\033Oy"; - } - - /** - * The state engine for a modifier key. Can be pressed, released, locked, - * and so on. - * - */ - private class ModifierKey { - - private int mState; - - private static final int UNPRESSED = 0; - - private static final int PRESSED = 1; - - private static final int RELEASED = 2; - - private static final int USED = 3; - - private static final int LOCKED = 4; - - /** - * Construct a modifier key. UNPRESSED by default. - * - */ - public ModifierKey() { - mState = UNPRESSED; - } - - public void onPress() { - switch (mState) { - case PRESSED: - // This is a repeat before use - break; - case RELEASED: - mState = LOCKED; - break; - case USED: - // This is a repeat after use - break; - case LOCKED: - mState = UNPRESSED; - break; - default: - mState = PRESSED; - break; - } - } - - public void onRelease() { - switch (mState) { - case USED: - mState = UNPRESSED; - break; - case PRESSED: - mState = RELEASED; - break; - default: - // Leave state alone - break; - } - } - - public void adjustAfterKeypress() { - switch (mState) { - case PRESSED: - mState = USED; - break; - case RELEASED: - mState = UNPRESSED; - break; - default: - // Leave state alone - break; - } - } - - public boolean isActive() { - return mState != UNPRESSED; - } - } - - private ModifierKey mAltKey = new ModifierKey(); - - private ModifierKey mCapKey = new ModifierKey(); - - private ModifierKey mControlKey = new ModifierKey(); - - private ModifierKey mFnKey = new ModifierKey(); - - private boolean mCapsLock; - - static public final int KEYCODE_OFFSET = 1000; - - /** - * Construct a term key listener. - * - */ - public TermKeyListener() { - initKeyCodes(); - } - - public void handleControlKey(boolean down) { - if (down) { - mControlKey.onPress(); - } else { - mControlKey.onRelease(); - } - } - - public void handleFnKey(boolean down) { - if (down) { - mFnKey.onPress(); - } else { - mFnKey.onRelease(); - } - } - - public int mapControlChar(int ch) { - int result = ch; - if (mControlKey.isActive()) { - // Search is the control key. - if (result >= 'a' && result <= 'z') { - result = (char) (result - 'a' + '\001'); - } else if (result >= '0' && result <= '9') { - result = (char) (result - '0' + '\001'); - } else if (result == ' ') { - result = 0; - } else if (result == '[') { - result = 27; - } else if (result == '\\') { - result = 28; - } else if (result == ']') { - result = 29; - } else if (result == '^') { - result = 30; // control-^ - } else if (result == '_') { - result = 31; - } else if ((result == '5')) { - result = '|'; - } else if ((result == '6')) { - result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_LEFT; - } else if ((result == '7')) { - result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_DOWN; - } else if ((result == '8')) { - result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_UP; - } else if ((result == '9')) { - result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_RIGHT; - } - } else if (mFnKey.isActive()) { - if (result == 'w') { - result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_UP; - } else if (result == 'a') { - result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_LEFT; - } else if (result == 's') { - result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_DOWN; - } else if (result == 'd') { - result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_RIGHT; - } else if (result == 'p') { - result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_PAGE_UP; - } else if (result == 'n') { - result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_PAGE_DOWN; - } else if (result == 't') { - result = KEYCODE_OFFSET + KeyEvent.KEYCODE_TAB; - } else if (result == 'l') { - result = '|'; - } else if (result == 'u') { - result = '_'; - } else if (result == 'e') { - result = 27; // ^[ (Esc) - } else if (result == '.') { - result = 28; // ^\ - } else if (result == '4') { - result = 29; // ^] - } else if (result == '3') { - result = 30; // control-^ - } else if (result == '2') { - result = 31; // ^_ - } - } - - if (result > -1) { - mAltKey.adjustAfterKeypress(); - mCapKey.adjustAfterKeypress(); - mControlKey.adjustAfterKeypress(); - mFnKey.adjustAfterKeypress(); - } - - return result; - } - - /** - * Handle a keyDown event. - * - * @param keyCode the keycode of the keyDown event - * - */ - public void keyDown(int keyCode, KeyEvent event, OutputStream out, boolean appMode) throws IOException { - if (handleKeyCode(keyCode, out, appMode)) { - return; - } - int result = -1; - switch (keyCode) { - case KeyEvent.KEYCODE_ALT_RIGHT: - case KeyEvent.KEYCODE_ALT_LEFT: - mAltKey.onPress(); - break; - - case KeyEvent.KEYCODE_SHIFT_LEFT: - case KeyEvent.KEYCODE_SHIFT_RIGHT: - mCapKey.onPress(); - break; - - case KEYCODE_CTRL_LEFT: - case KEYCODE_CTRL_RIGHT: - mControlKey.onPress(); - break; - - case KEYCODE_CAPS_LOCK: - if (event.getRepeatCount() == 0) { - mCapsLock = !mCapsLock; - } - break; - - default: { - result = event.getUnicodeChar( - (mCapKey.isActive() || mCapsLock ? KeyEvent.META_SHIFT_ON : 0) | - (mAltKey.isActive() ? KeyEvent.META_ALT_ON : 0)); - break; - } - } - - result = mapControlChar(result); - - if (result >= KEYCODE_OFFSET) { - handleKeyCode(result - KEYCODE_OFFSET, out, appMode); - } else if (result >= 0) { - out.write(result); - } - } - - public boolean handleKeyCode(int keyCode, OutputStream out, boolean appMode) throws IOException { - if (keyCode >= 0 && keyCode < mKeyCodes.length) { - String code = null; - if (appMode) { - code = mAppKeyCodes[keyCode]; - } - if (code == null) { - code = mKeyCodes[keyCode]; - } - if (code != null) { - int length = code.length(); - for (int i = 0; i < length; i++) { - out.write(code.charAt(i)); - } - return true; - } - } - return false; - } - - /** - * Handle a keyUp event. - * - * @param keyCode the keyCode of the keyUp event - */ - public void keyUp(int keyCode) { - switch (keyCode) { - case KeyEvent.KEYCODE_ALT_LEFT: - case KeyEvent.KEYCODE_ALT_RIGHT: - mAltKey.onRelease(); - break; - case KeyEvent.KEYCODE_SHIFT_LEFT: - case KeyEvent.KEYCODE_SHIFT_RIGHT: - mCapKey.onRelease(); - break; - - case KEYCODE_CTRL_LEFT: - case KEYCODE_CTRL_RIGHT: - mControlKey.onRelease(); - break; - - default: - // Ignore other keyUps - break; - } - } -} diff --git a/src/jackpal/androidterm/TermDebug.java b/src/jackpal/androidterm/TermDebug.java new file mode 100644 index 000000000..56d2fb289 --- /dev/null +++ b/src/jackpal/androidterm/TermDebug.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jackpal.androidterm2; + +/** + * Debug settings. + */ + +public class TermDebug { + /** + * Set to true to add debugging code and logging. + */ + public static final boolean DEBUG = false; + + /** + * Set to true to log IME calls. + */ + public static final boolean LOG_IME = DEBUG && false; + + /** + * Set to true to log each character received from the remote process to the + * android log, which makes it easier to debug some kinds of problems with + * emulating escape sequences and control codes. + */ + public static final boolean LOG_CHARACTERS_FLAG = DEBUG && false; + + /** + * Set to true to log unknown escape sequences. + */ + public static final boolean LOG_UNKNOWN_ESCAPE_SEQUENCES = DEBUG && false; + + /** + * The tag we use when logging, so that our messages can be distinguished + * from other messages in the log. Public because it's used by several + * classes. + */ + public static final String LOG_TAG = "Term"; +} diff --git a/src/jackpal/androidterm/TermService.java b/src/jackpal/androidterm/TermService.java index 5cc2f4118..42e010a05 100644 --- a/src/jackpal/androidterm/TermService.java +++ b/src/jackpal/androidterm/TermService.java @@ -16,7 +16,10 @@ package jackpal.androidterm2; +import java.util.ArrayList; + import android.app.Service; +import android.os.Binder; import android.os.IBinder; import android.content.Intent; import android.util.Log; @@ -24,6 +27,9 @@ import android.app.NotificationManager; import android.app.PendingIntent; +import jackpal.androidterm2.session.TermSession; +import jackpal.androidterm2.util.ServiceForegroundCompat; + public class TermService extends Service { /* Parallels the value of START_STICKY on API Level >= 5 */ @@ -32,6 +38,16 @@ public class TermService extends Service private static final int RUNNING_NOTIFICATION = 1; private ServiceForegroundCompat compat; + private ArrayList mTermSessions; + + public class TSBinder extends Binder { + TermService getService() { + Log.i("TermService", "Activity binding to service"); + return TermService.this; + } + } + private final IBinder mTSBinder = new TSBinder(); + @Override public void onStart(Intent intent, int flags) { } @@ -43,12 +59,14 @@ public int onStartCommand(Intent intent, int flags, int startId) { @Override public IBinder onBind(Intent intent) { - return null; + Log.i("TermService", "Activity called onBind()"); + return mTSBinder; } @Override public void onCreate() { compat = new ServiceForegroundCompat(this); + mTermSessions = new ArrayList(); /* Put the service in the foreground. */ Notification notification = new Notification(R.drawable.app_terminal, getText(R.string.service_notify_text), System.currentTimeMillis()); @@ -59,13 +77,21 @@ public void onCreate() { notification.setLatestEventInfo(this, getText(R.string.application_terminal), getText(R.string.service_notify_text), pendingIntent); compat.startForeground(RUNNING_NOTIFICATION, notification); - Log.d(Term.LOG_TAG, "TermService started"); + Log.d(TermDebug.LOG_TAG, "TermService started"); return; } @Override public void onDestroy() { compat.stopForeground(true); + for (TermSession session : mTermSessions) { + session.finish(); + } + mTermSessions.clear(); return; } + + public ArrayList getSessions() { + return mTermSessions; + } } diff --git a/src/jackpal/androidterm/TermViewFlipper.java b/src/jackpal/androidterm/TermViewFlipper.java new file mode 100644 index 000000000..d92b84839 --- /dev/null +++ b/src/jackpal/androidterm/TermViewFlipper.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2011 Steven Luo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jackpal.androidterm2; + +import java.util.Iterator; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.widget.Toast; +import android.widget.ViewFlipper; + +public class TermViewFlipper extends ViewFlipper implements Iterable { + private Context context; + private Toast mToast; + + class ViewFlipperIterator implements Iterator { + int pos = 0; + + public boolean hasNext() { + return (pos < getChildCount()); + } + + public View next() { + return getChildAt(pos++); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + } + + public TermViewFlipper(Context context) { + super(context); + this.context = context; + } + + public TermViewFlipper(Context context, AttributeSet attrs) { + super(context, attrs); + this.context = context; + } + + public Iterator iterator() { + return new ViewFlipperIterator(); + } + + public void pauseCurrentView() { + EmulatorView view = (EmulatorView) getCurrentView(); + if (view == null) { + return; + } + view.onPause(); + } + + public void resumeCurrentView() { + EmulatorView view = (EmulatorView) getCurrentView(); + if (view == null) { + return; + } + view.onResume(); + } + + private void showTitle() { + if (getChildCount() == 0) { + return; + } + String title = "Window " + (getDisplayedChild()+1); + if (mToast == null) { + mToast = Toast.makeText(context, title, Toast.LENGTH_SHORT); + } else { + mToast.setText(title); + } + mToast.show(); + } + + @Override + public void showPrevious() { + pauseCurrentView(); + super.showPrevious(); + showTitle(); + resumeCurrentView(); + } + + @Override + public void showNext() { + pauseCurrentView(); + super.showNext(); + showTitle(); + resumeCurrentView(); + } + + @Override + public void setDisplayedChild(int position) { + pauseCurrentView(); + super.setDisplayedChild(position); + showTitle(); + resumeCurrentView(); + } +} diff --git a/src/jackpal/androidterm/WindowList.java b/src/jackpal/androidterm/WindowList.java new file mode 100644 index 000000000..89e770a2a --- /dev/null +++ b/src/jackpal/androidterm/WindowList.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2011 Steven Luo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jackpal.androidterm2; + +import java.util.ArrayList; + +import android.app.ListActivity; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.os.IBinder; +import android.util.AttributeSet; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.TextView; + +import jackpal.androidterm2.session.TermSession; + +public class WindowList extends ListActivity { + private ArrayList sessions; + private WindowListAdapter mWindowListAdapter; + private TermService mTermService; + + class WindowListAdapter extends BaseAdapter { + private LayoutInflater inflater = getLayoutInflater(); + + public int getCount() { + return sessions.size(); + } + + public Object getItem(int position) { + return sessions.get(position); + } + + public long getItemId(int position) { + return position; + } + + public View getView(int position, View convertView, ViewGroup parent) { + View child = inflater.inflate(R.layout.window_list_item, parent, false); + TextView label = (TextView) child.findViewById(R.id.window_list_label); + label.setText("Window " + (position+1)); + + View close = child.findViewById(R.id.window_list_close); + final TermService service = mTermService; + final int closePosition = position; + close.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + TermSession session = service.getSessions().remove(closePosition); + if (session != null) { + session.finish(); + notifyDataSetChanged(); + } + } + }); + + return child; + } + } + + /** + * View which isn't automatically in the pressed state if its parent is + * pressed. This allows the window's entry to be pressed without the close + * button being triggered. + * Idea and code shamelessly borrowed from the Android browser's tabs list. + */ + private static class CloseButton extends ImageView { + public CloseButton(Context context) { + super(context); + } + + public CloseButton(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public CloseButton(Context context, AttributeSet attrs, int style) { + super(context, attrs, style); + } + + @Override + public void setPressed(boolean pressed) { + if (pressed && ((View) getParent()).isPressed()) { + return; + } + super.setPressed(pressed); + } + } + + private ServiceConnection mTSConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + TermService.TSBinder binder = (TermService.TSBinder) service; + mTermService = binder.getService(); + populateList(); + } + + public void onServiceDisconnected(ComponentName arg0) { + mTermService = null; + } + }; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + setTitle(R.string.window_list); + + ListView listView = getListView(); + View newWindow = getLayoutInflater().inflate(R.layout.window_list_new_window, listView, false); + listView.addHeaderView(newWindow, null, true); + + setResult(RESULT_CANCELED); + } + + @Override + protected void onResume() { + super.onResume(); + + Intent TSIntent = new Intent(this, TermService.class); + if (!bindService(TSIntent, mTSConnection, BIND_AUTO_CREATE)) { + Log.w(TermDebug.LOG_TAG, "bind to service failed!"); + } + } + + @Override + protected void onPause() { + super.onPause(); + + unbindService(mTSConnection); + } + + private void populateList() { + sessions = mTermService.getSessions(); + + if (mWindowListAdapter == null) { + setListAdapter(new WindowListAdapter()); + } else { + mWindowListAdapter.notifyDataSetChanged(); + } + } + + @Override + protected void onListItemClick(ListView l, View v, int position, long id) { + Intent data = new Intent(); + data.putExtra(Term.EXTRA_WINDOW_ID, position-1); + setResult(RESULT_OK, data); + finish(); + } +} diff --git a/src/jackpal/androidterm/model/Screen.java b/src/jackpal/androidterm/model/Screen.java new file mode 100644 index 000000000..697c3bec0 --- /dev/null +++ b/src/jackpal/androidterm/model/Screen.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jackpal.androidterm2.model; + +/** + * An abstract screen interface. A terminal screen stores lines of text. (The + * reason to abstract it is to allow different implementations, and to hide + * implementation details from clients.) + */ +public interface Screen { + + /** + * Set line wrap flag for a given row. Affects how lines are logically + * wrapped when changing screen size or converting to a transcript. + */ + void setLineWrap(int row); + + /** + * Store byte b into the screen at location (x, y) + * + * @param x X coordinate (also known as column) + * @param y Y coordinate (also known as row) + * @param b ASCII character to store + * @param foreColor the foreground color + * @param backColor the background color + */ + void set(int x, int y, byte b, int foreColor, int backColor); + + /** + * Scroll the screen down one line. To scroll the whole screen of a 24 line + * screen, the arguments would be (0, 24). + * + * @param topMargin First line that is scrolled. + * @param bottomMargin One line after the last line that is scrolled. + */ + void scroll(int topMargin, int bottomMargin, int foreColor, int backColor); + + /** + * Block copy characters from one position in the screen to another. The two + * positions can overlap. All characters of the source and destination must + * be within the bounds of the screen, or else an InvalidParemeterException + * will be thrown. + * + * @param sx source X coordinate + * @param sy source Y coordinate + * @param w width + * @param h height + * @param dx destination X coordinate + * @param dy destination Y coordinate + */ + void blockCopy(int sx, int sy, int w, int h, int dx, int dy); + + /** + * Block set characters. All characters must be within the bounds of the + * screen, or else and InvalidParemeterException will be thrown. Typically + * this is called with a "val" argument of 32 to clear a block of + * characters. + * + * @param sx source X + * @param sy source Y + * @param w width + * @param h height + * @param val value to set. + * @param foreColor the foreground color + * @param backColor the background color + */ + void blockSet(int sx, int sy, int w, int h, int val, int foreColor, int + backColor); + + /** + * Get the contents of the transcript buffer as a text string. + * + * @return the contents of the transcript buffer. + */ + String getTranscriptText(); + + /** + * Get the selected text inside transcript buffer as a text string. + * @param x1 Selection start + * @param y1 Selection start + * @param x2 Selection end + * @param y2 Selection end + * @return the contents of the transcript buffer. + */ + String getSelectedText(int x1, int y1, int x2, int y2); + + /** + * Resize the screen + * @param columns + * @param rows + */ + void resize(int columns, int rows, int foreColor, int backColor); +} diff --git a/src/jackpal/androidterm/model/TextRenderer.java b/src/jackpal/androidterm/model/TextRenderer.java new file mode 100644 index 000000000..36b50f939 --- /dev/null +++ b/src/jackpal/androidterm/model/TextRenderer.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jackpal.androidterm2.model; + +import android.graphics.Canvas; + +/** + * Text renderer interface + */ + +public interface TextRenderer { + float getCharacterWidth(); + int getCharacterHeight(); + void drawTextRun(Canvas canvas, float x, float y, + int lineOffset, char[] text, + int index, int count, boolean cursor, int foreColor, int backColor); +} diff --git a/src/jackpal/androidterm/model/UpdateCallback.java b/src/jackpal/androidterm/model/UpdateCallback.java new file mode 100644 index 000000000..9698e0c7a --- /dev/null +++ b/src/jackpal/androidterm/model/UpdateCallback.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jackpal.androidterm2.model; + +/** + * Callback to be invoked when updates to a TermSession's transcript occur. + */ +public interface UpdateCallback { + void onUpdate(); +} diff --git a/src/jackpal/androidterm/session/TermSession.java b/src/jackpal/androidterm/session/TermSession.java new file mode 100644 index 000000000..bbbf86a1d --- /dev/null +++ b/src/jackpal/androidterm/session/TermSession.java @@ -0,0 +1,273 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jackpal.androidterm2.session; + +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; + +import android.os.Handler; +import android.os.Message; +import android.util.Log; + +import jackpal.androidterm2.Exec; +import jackpal.androidterm2.TermDebug; +import jackpal.androidterm2.model.UpdateCallback; +import jackpal.androidterm2.util.ByteQueue; +import jackpal.androidterm2.util.TermSettings; + +/** + * A terminal session, consisting of a TerminalEmulator, a TranscriptScreen, + * the PID of the process attached to the session, and the I/O streams used to + * talk to the process. + */ +public class TermSession { + private TermSettings mSettings; + private UpdateCallback mNotify; + + private int mProcId; + private FileDescriptor mTermFd; + private FileOutputStream mTermOut; + private FileInputStream mTermIn; + + private TranscriptScreen mTranscriptScreen; + private TerminalEmulator mEmulator; + + private Thread mPollingThread; + private ByteQueue mByteQueue; + private byte[] mReceiveBuffer; + + private static final int DEFAULT_COLUMNS = 80; + private static final int DEFAULT_ROWS = 24; + private static final String DEFAULT_SHELL = "/system/bin/sh -"; + private static final String DEFAULT_INITIAL_COMMAND = + "export PATH=/data/local/bin:$PATH"; + + // Number of rows in the transcript + private static final int TRANSCRIPT_ROWS = 10000; + + private static final int NEW_INPUT = 1; + + private boolean mIsRunning = false; + private Handler mMsgHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + if (!mIsRunning) { + return; + } + if (msg.what == NEW_INPUT) { + readFromProcess(); + } + } + }; + + public TermSession(TermSettings settings, UpdateCallback notify, String initialCommand) { + mSettings = settings; + mNotify = notify; + + int[] processId = new int[1]; + + createSubprocess(processId); + mProcId = processId[0]; + mTermOut = new FileOutputStream(mTermFd); + mTermIn = new FileInputStream(mTermFd); + + mTranscriptScreen = new TranscriptScreen(DEFAULT_COLUMNS, TRANSCRIPT_ROWS, DEFAULT_ROWS, 0, 7); + mEmulator = new TerminalEmulator(mTranscriptScreen, DEFAULT_COLUMNS, DEFAULT_ROWS, mTermOut); + + mIsRunning = true; + + Thread watcher = new Thread() { + @Override + public void run() { + Log.i(TermDebug.LOG_TAG, "waiting for: " + mProcId); + int result = Exec.waitFor(mProcId); + Log.i(TermDebug.LOG_TAG, "Subprocess exited: " + result); + mMsgHandler.sendEmptyMessage(result); + } + }; + watcher.setName("Process watcher"); + watcher.start(); + + mReceiveBuffer = new byte[4 * 1024]; + mByteQueue = new ByteQueue(4 * 1024); + + mPollingThread = new Thread() { + private byte[] mBuffer = new byte[4096]; + + @Override + public void run() { + try { + while(true) { + int read = mTermIn.read(mBuffer); + if (read == -1) { + // EOF -- process exited + return; + } + mByteQueue.write(mBuffer, 0, read); + mMsgHandler.sendMessage( + mMsgHandler.obtainMessage(NEW_INPUT)); + } + } catch (IOException e) { + } catch (InterruptedException e) { + } + } + }; + mPollingThread.setName("Input reader"); + mPollingThread.start(); + + sendInitialCommand(initialCommand); + } + + private void sendInitialCommand(String initialCommand) { + if (initialCommand == null || initialCommand.equals("")) { + initialCommand = DEFAULT_INITIAL_COMMAND; + } + if (initialCommand.length() > 0) { + write(initialCommand + '\r'); + } + } + + public void write(String data) { + try { + mTermOut.write(data.getBytes()); + mTermOut.flush(); + } catch (IOException e) { + // Ignore exception + // We don't really care if the receiver isn't listening. + // We just make a best effort to answer the query. + } + } + + private void createSubprocess(int[] processId) { + String shell = mSettings.getShell(); + if (shell == null || shell.equals("")) { + shell = DEFAULT_SHELL; + } + ArrayList args = parse(shell); + String arg0 = args.get(0); + String arg1 = null; + String arg2 = null; + if (args.size() >= 2) { + arg1 = args.get(1); + } + if (args.size() >= 3) { + arg2 = args.get(2); + } + mTermFd = Exec.createSubprocess(arg0, arg1, arg2, processId); + } + + private ArrayList parse(String cmd) { + final int PLAIN = 0; + final int WHITESPACE = 1; + final int INQUOTE = 2; + int state = WHITESPACE; + ArrayList result = new ArrayList(); + int cmdLen = cmd.length(); + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < cmdLen; i++) { + char c = cmd.charAt(i); + if (state == PLAIN) { + if (Character.isWhitespace(c)) { + result.add(builder.toString()); + builder.delete(0,builder.length()); + state = WHITESPACE; + } else if (c == '"') { + state = INQUOTE; + } else { + builder.append(c); + } + } else if (state == WHITESPACE) { + if (Character.isWhitespace(c)) { + // do nothing + } else if (c == '"') { + state = INQUOTE; + } else { + state = PLAIN; + builder.append(c); + } + } else if (state == INQUOTE) { + if (c == '\\') { + if (i + 1 < cmdLen) { + i += 1; + builder.append(cmd.charAt(i)); + } + } else if (c == '"') { + state = PLAIN; + } else { + builder.append(c); + } + } + } + if (builder.length() > 0) { + result.add(builder.toString()); + } + return result; + } + + public FileOutputStream getTermOut() { + return mTermOut; + } + + public TranscriptScreen getTranscriptScreen() { + return mTranscriptScreen; + } + + public TerminalEmulator getEmulator() { + return mEmulator; + } + + public void setUpdateCallback(UpdateCallback notify) { + mNotify = notify; + } + + public void updateSize(int columns, int rows) { + // Inform the attached pty of our new size: + Exec.setPtyWindowSize(mTermFd, rows, columns, 0, 0); + mEmulator.updateSize(columns, rows); + } + + public String getTranscriptText() { + return mTranscriptScreen.getTranscriptText(); + } + + /** + * Look for new input from the ptty, send it to the terminal emulator. + */ + private void readFromProcess() { + int bytesAvailable = mByteQueue.getBytesAvailable(); + int bytesToRead = Math.min(bytesAvailable, mReceiveBuffer.length); + try { + int bytesRead = mByteQueue.read(mReceiveBuffer, 0, bytesToRead); + mEmulator.append(mReceiveBuffer, 0, bytesRead); + } catch (InterruptedException e) { + } + + if (mNotify != null) { + mNotify.onUpdate(); + } + } + + public void finish() { + Exec.hangupProcessGroup(mProcId); + Exec.close(mTermFd); + mIsRunning = false; + mTranscriptScreen.finish(); + } +} diff --git a/src/jackpal/androidterm/session/TerminalEmulator.java b/src/jackpal/androidterm/session/TerminalEmulator.java new file mode 100644 index 000000000..296cfa1f8 --- /dev/null +++ b/src/jackpal/androidterm/session/TerminalEmulator.java @@ -0,0 +1,1233 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jackpal.androidterm2.session; + +import java.io.FileOutputStream; +import java.io.IOException; + +import android.util.Log; + +import jackpal.androidterm2.TermDebug; +import jackpal.androidterm2.model.Screen; + +/** + * Renders text into a screen. Contains all the terminal-specific knowlege and + * state. Emulates a subset of the X Window System xterm terminal, which in turn + * is an emulator for a subset of the Digital Equipment Corporation vt100 + * terminal. Missing functionality: text attributes (bold, underline, reverse + * video, color) alternate screen cursor key and keypad escape sequences. + */ +public class TerminalEmulator { + + /** + * The cursor row. Numbered 0..mRows-1. + */ + private int mCursorRow; + + /** + * The cursor column. Numbered 0..mColumns-1. + */ + private int mCursorCol; + + /** + * The number of character rows in the terminal screen. + */ + private int mRows; + + /** + * The number of character columns in the terminal screen. + */ + private int mColumns; + + /** + * Used to send data to the remote process. Needed to implement the various + * "report" escape sequences. + */ + private FileOutputStream mTermOut; + + /** + * Stores the characters that appear on the screen of the emulated terminal. + */ + private Screen mScreen; + + /** + * Keeps track of the current argument of the current escape sequence. + * Ranges from 0 to MAX_ESCAPE_PARAMETERS-1. (Typically just 0 or 1.) + */ + private int mArgIndex; + + /** + * The number of parameter arguments. This name comes from the ANSI standard + * for terminal escape codes. + */ + private static final int MAX_ESCAPE_PARAMETERS = 16; + + /** + * Holds the arguments of the current escape sequence. + */ + private int[] mArgs = new int[MAX_ESCAPE_PARAMETERS]; + + // Escape processing states: + + /** + * Escape processing state: Not currently in an escape sequence. + */ + private static final int ESC_NONE = 0; + + /** + * Escape processing state: Have seen an ESC character + */ + private static final int ESC = 1; + + /** + * Escape processing state: Have seen ESC POUND + */ + private static final int ESC_POUND = 2; + + /** + * Escape processing state: Have seen ESC and a character-set-select char + */ + private static final int ESC_SELECT_LEFT_PAREN = 3; + + /** + * Escape processing state: Have seen ESC and a character-set-select char + */ + private static final int ESC_SELECT_RIGHT_PAREN = 4; + + /** + * Escape processing state: ESC [ + */ + private static final int ESC_LEFT_SQUARE_BRACKET = 5; + + /** + * Escape processing state: ESC [ ? + */ + private static final int ESC_LEFT_SQUARE_BRACKET_QUESTION_MARK = 6; + + /** + * True if the current escape sequence should continue, false if the current + * escape sequence should be terminated. Used when parsing a single + * character. + */ + private boolean mContinueSequence; + + /** + * The current state of the escape sequence state machine. + */ + private int mEscapeState; + + /** + * Saved state of the cursor row, Used to implement the save/restore cursor + * position escape sequences. + */ + private int mSavedCursorRow; + + /** + * Saved state of the cursor column, Used to implement the save/restore + * cursor position escape sequences. + */ + private int mSavedCursorCol; + + // DecSet booleans + + /** + * This mask indicates 132-column mode is set. (As opposed to 80-column + * mode.) + */ + private static final int K_132_COLUMN_MODE_MASK = 1 << 3; + + /** + * This mask indicates that origin mode is set. (Cursor addressing is + * relative to the absolute screen size, rather than the currently set top + * and bottom margins.) + */ + private static final int K_ORIGIN_MODE_MASK = 1 << 6; + + /** + * This mask indicates that wraparound mode is set. (As opposed to + * stop-at-right-column mode.) + */ + private static final int K_WRAPAROUND_MODE_MASK = 1 << 7; + + /** + * Holds multiple DECSET flags. The data is stored this way, rather than in + * separate booleans, to make it easier to implement the save-and-restore + * semantics. The various k*ModeMask masks can be used to extract and modify + * the individual flags current states. + */ + private int mDecFlags; + + /** + * Saves away a snapshot of the DECSET flags. Used to implement save and + * restore escape sequences. + */ + private int mSavedDecFlags; + + // Modes set with Set Mode / Reset Mode + + /** + * True if insert mode (as opposed to replace mode) is active. In insert + * mode new characters are inserted, pushing existing text to the right. + */ + private boolean mInsertMode; + + /** + * Automatic newline mode. Configures whether pressing return on the + * keyboard automatically generates a return as well. Not currently + * implemented. + */ + private boolean mAutomaticNewlineMode; + + /** + * An array of tab stops. mTabStop[i] is true if there is a tab stop set for + * column i. + */ + private boolean[] mTabStop; + + // The margins allow portions of the screen to be locked. + + /** + * The top margin of the screen, for scrolling purposes. Ranges from 0 to + * mRows-2. + */ + private int mTopMargin; + + /** + * The bottom margin of the screen, for scrolling purposes. Ranges from + * mTopMargin + 2 to mRows. (Defines the first row after the scrolling + * region. + */ + private int mBottomMargin; + + /** + * True if the next character to be emitted will be automatically wrapped to + * the next line. Used to disambiguate the case where the cursor is + * positioned on column mColumns-1. + */ + private boolean mAboutToAutoWrap; + + /** + * Used for debugging, counts how many chars have been processed. + */ + private int mProcessedCharCount; + + /** + * Foreground color, 0..7, mask with 8 for bold + */ + private int mForeColor; + + /** + * Background color, 0..7, mask with 8 for underline + */ + private int mBackColor; + + private boolean mInverseColors; + + private boolean mbKeypadApplicationMode; + + private boolean mAlternateCharSet; + + /** + * Used for moving selection up along with the scrolling text + */ + private int mScrollCounter = 0; + + /** + * Construct a terminal emulator that uses the supplied screen + * + * @param screen the screen to render characters into. + * @param columns the number of columns to emulate + * @param rows the number of rows to emulate + * @param termOut the output file descriptor that talks to the pseudo-tty. + */ + public TerminalEmulator(Screen screen, int columns, int rows, + FileOutputStream termOut) { + mScreen = screen; + mRows = rows; + mColumns = columns; + mTabStop = new boolean[mColumns]; + mTermOut = termOut; + reset(); + } + + public void updateSize(int columns, int rows) { + if (mRows == rows && mColumns == columns) { + return; + } + if (columns <= 0) { + throw new IllegalArgumentException("rows:" + columns); + } + + if (rows <= 0) { + throw new IllegalArgumentException("rows:" + rows); + } + + String transcriptText = mScreen.getTranscriptText(); + + mScreen.resize(columns, rows, mForeColor, mBackColor); + + if (mRows != rows) { + mRows = rows; + mTopMargin = 0; + mBottomMargin = mRows; + } + if (mColumns != columns) { + int oldColumns = mColumns; + mColumns = columns; + boolean[] oldTabStop = mTabStop; + mTabStop = new boolean[mColumns]; + int toTransfer = Math.min(oldColumns, columns); + System.arraycopy(oldTabStop, 0, mTabStop, 0, toTransfer); + while (mCursorCol >= columns) { + mCursorCol -= columns; + mCursorRow = Math.min(mBottomMargin-1, mCursorRow + 1); + } + } + mCursorRow = 0; + mCursorCol = 0; + mAboutToAutoWrap = false; + + int end = transcriptText.length()-1; + while ((end >= 0) && transcriptText.charAt(end) == '\n') { + end--; + } + for(int i = 0; i <= end; i++) { + byte c = (byte) transcriptText.charAt(i); + if (c == '\n') { + setCursorCol(0); + doLinefeed(); + } else { + emit(c); + } + } + } + + /** + * Get the cursor's current row. + * + * @return the cursor's current row. + */ + public final int getCursorRow() { + return mCursorRow; + } + + /** + * Get the cursor's current column. + * + * @return the cursor's current column. + */ + public final int getCursorCol() { + return mCursorCol; + } + + public final boolean getKeypadApplicationMode() { + return mbKeypadApplicationMode; + } + + private void setDefaultTabStops() { + for (int i = 0; i < mColumns; i++) { + mTabStop[i] = (i & 7) == 0 && i != 0; + } + } + + /** + * Accept bytes (typically from the pseudo-teletype) and process them. + * + * @param buffer a byte array containing the bytes to be processed + * @param base the first index of the array to process + * @param length the number of bytes in the array to process + */ + public void append(byte[] buffer, int base, int length) { + for (int i = 0; i < length; i++) { + byte b = buffer[base + i]; + try { + if (TermDebug.LOG_CHARACTERS_FLAG) { + char printableB = (char) b; + if (b < 32 || b > 126) { + printableB = ' '; + } + Log.w(TermDebug.LOG_TAG, "'" + Character.toString(printableB) + + "' (" + Integer.toString(b) + ")"); + } + process(b); + mProcessedCharCount++; + } catch (Exception e) { + Log.e(TermDebug.LOG_TAG, "Exception while processing character " + + Integer.toString(mProcessedCharCount) + " code " + + Integer.toString(b), e); + } + } + } + + private void process(byte b) { + switch (b) { + case 0: // NUL + // Do nothing + break; + + case 7: // BEL + // Do nothing + break; + + case 8: // BS + setCursorCol(Math.max(0, mCursorCol - 1)); + break; + + case 9: // HT + // Move to next tab stop, but not past edge of screen + setCursorCol(nextTabStop(mCursorCol)); + break; + + case 13: + setCursorCol(0); + break; + + case 10: // CR + case 11: // VT + case 12: // LF + doLinefeed(); + break; + + case 14: // SO: + setAltCharSet(true); + break; + + case 15: // SI: + setAltCharSet(false); + break; + + + case 24: // CAN + case 26: // SUB + if (mEscapeState != ESC_NONE) { + mEscapeState = ESC_NONE; + emit((byte) 127); + } + break; + + case 27: // ESC + // Always starts an escape sequence + startEscapeSequence(ESC); + break; + + case (byte) 0x9b: // CSI + startEscapeSequence(ESC_LEFT_SQUARE_BRACKET); + break; + + default: + mContinueSequence = false; + switch (mEscapeState) { + case ESC_NONE: + if (b >= 32) { + emit(b); + } + break; + + case ESC: + doEsc(b); + break; + + case ESC_POUND: + doEscPound(b); + break; + + case ESC_SELECT_LEFT_PAREN: + doEscSelectLeftParen(b); + break; + + case ESC_SELECT_RIGHT_PAREN: + doEscSelectRightParen(b); + break; + + case ESC_LEFT_SQUARE_BRACKET: + doEscLeftSquareBracket(b); + break; + + case ESC_LEFT_SQUARE_BRACKET_QUESTION_MARK: + doEscLSBQuest(b); + break; + + default: + unknownSequence(b); + break; + } + if (!mContinueSequence) { + mEscapeState = ESC_NONE; + } + break; + } + } + + private void setAltCharSet(boolean alternateCharSet) { + mAlternateCharSet = alternateCharSet; + } + + private int nextTabStop(int cursorCol) { + for (int i = cursorCol; i < mColumns; i++) { + if (mTabStop[i]) { + return i; + } + } + return mColumns - 1; + } + + private void doEscLSBQuest(byte b) { + int mask = getDecFlagsMask(getArg0(0)); + switch (b) { + case 'h': // Esc [ ? Pn h - DECSET + mDecFlags |= mask; + break; + + case 'l': // Esc [ ? Pn l - DECRST + mDecFlags &= ~mask; + break; + + case 'r': // Esc [ ? Pn r - restore + mDecFlags = (mDecFlags & ~mask) | (mSavedDecFlags & mask); + break; + + case 's': // Esc [ ? Pn s - save + mSavedDecFlags = (mSavedDecFlags & ~mask) | (mDecFlags & mask); + break; + + default: + parseArg(b); + break; + } + + // 132 column mode + if ((mask & K_132_COLUMN_MODE_MASK) != 0) { + // We don't actually set 132 cols, but we do want the + // side effect of clearing the screen and homing the cursor. + blockClear(0, 0, mColumns, mRows); + setCursorRowCol(0, 0); + } + + // origin mode + if ((mask & K_ORIGIN_MODE_MASK) != 0) { + // Home the cursor. + setCursorPosition(0, 0); + } + } + + private int getDecFlagsMask(int argument) { + if (argument >= 1 && argument <= 9) { + return (1 << argument); + } + + return 0; + } + + private void startEscapeSequence(int escapeState) { + mEscapeState = escapeState; + mArgIndex = 0; + for (int j = 0; j < MAX_ESCAPE_PARAMETERS; j++) { + mArgs[j] = -1; + } + } + + private void doLinefeed() { + int newCursorRow = mCursorRow + 1; + if (newCursorRow >= mBottomMargin) { + scroll(); + newCursorRow = mBottomMargin - 1; + } + setCursorRow(newCursorRow); + } + + private void continueSequence() { + mContinueSequence = true; + } + + private void continueSequence(int state) { + mEscapeState = state; + mContinueSequence = true; + } + + private void doEscSelectLeftParen(byte b) { + doSelectCharSet(true, b); + } + + private void doEscSelectRightParen(byte b) { + doSelectCharSet(false, b); + } + + private void doSelectCharSet(boolean isG0CharSet, byte b) { + switch (b) { + case 'A': // United Kingdom character set + break; + case 'B': // ASCII set + break; + case '0': // Special Graphics + break; + case '1': // Alternate character set + break; + case '2': + break; + default: + unknownSequence(b); + } + } + + private void doEscPound(byte b) { + switch (b) { + case '8': // Esc # 8 - DECALN alignment test + mScreen.blockSet(0, 0, mColumns, mRows, 'E', + getForeColor(), getBackColor()); + break; + + default: + unknownSequence(b); + break; + } + } + + private void doEsc(byte b) { + switch (b) { + case '#': + continueSequence(ESC_POUND); + break; + + case '(': + continueSequence(ESC_SELECT_LEFT_PAREN); + break; + + case ')': + continueSequence(ESC_SELECT_RIGHT_PAREN); + break; + + case '7': // DECSC save cursor + mSavedCursorRow = mCursorRow; + mSavedCursorCol = mCursorCol; + break; + + case '8': // DECRC restore cursor + setCursorRowCol(mSavedCursorRow, mSavedCursorCol); + break; + + case 'D': // INDEX + doLinefeed(); + break; + + case 'E': // NEL + setCursorCol(0); + doLinefeed(); + break; + + case 'F': // Cursor to lower-left corner of screen + setCursorRowCol(0, mBottomMargin - 1); + break; + + case 'H': // Tab set + mTabStop[mCursorCol] = true; + break; + + case 'M': // Reverse index + if (mCursorRow <= mTopMargin) { + mScreen.blockCopy(0, mTopMargin, mColumns, mBottomMargin + - (mTopMargin + 1), 0, mTopMargin + 1); + blockClear(0, mTopMargin, mColumns); + } else { + mCursorRow--; + } + + break; + + case 'N': // SS2 + unimplementedSequence(b); + break; + + case '0': // SS3 + unimplementedSequence(b); + break; + + case 'P': // Device control string + unimplementedSequence(b); + break; + + case 'Z': // return terminal ID + sendDeviceAttributes(); + break; + + case '[': + continueSequence(ESC_LEFT_SQUARE_BRACKET); + break; + + case '=': // DECKPAM + mbKeypadApplicationMode = true; + break; + + case '>' : // DECKPNM + mbKeypadApplicationMode = false; + break; + + default: + unknownSequence(b); + break; + } + } + + private void doEscLeftSquareBracket(byte b) { + switch (b) { + case '@': // ESC [ Pn @ - ICH Insert Characters + { + int charsAfterCursor = mColumns - mCursorCol; + int charsToInsert = Math.min(getArg0(1), charsAfterCursor); + int charsToMove = charsAfterCursor - charsToInsert; + mScreen.blockCopy(mCursorCol, mCursorRow, charsToMove, 1, + mCursorCol + charsToInsert, mCursorRow); + blockClear(mCursorCol, mCursorRow, charsToInsert); + } + break; + + case 'A': // ESC [ Pn A - Cursor Up + setCursorRow(Math.max(mTopMargin, mCursorRow - getArg0(1))); + break; + + case 'B': // ESC [ Pn B - Cursor Down + setCursorRow(Math.min(mBottomMargin - 1, mCursorRow + getArg0(1))); + break; + + case 'C': // ESC [ Pn C - Cursor Right + setCursorCol(Math.min(mColumns - 1, mCursorCol + getArg0(1))); + break; + + case 'D': // ESC [ Pn D - Cursor Left + setCursorCol(Math.max(0, mCursorCol - getArg0(1))); + break; + + case 'G': // ESC [ Pn G - Cursor Horizontal Absolute + setCursorCol(Math.min(Math.max(1, getArg0(1)), mColumns) - 1); + break; + + case 'H': // ESC [ Pn ; H - Cursor Position + setHorizontalVerticalPosition(); + break; + + case 'J': // ESC [ Pn J - Erase in Display + switch (getArg0(0)) { + case 0: // Clear below + blockClear(mCursorCol, mCursorRow, mColumns - mCursorCol); + blockClear(0, mCursorRow + 1, mColumns, + mBottomMargin - (mCursorRow + 1)); + break; + + case 1: // Erase from the start of the screen to the cursor. + blockClear(0, mTopMargin, mColumns, mCursorRow - mTopMargin); + blockClear(0, mCursorRow, mCursorCol + 1); + break; + + case 2: // Clear all + blockClear(0, mTopMargin, mColumns, mBottomMargin - mTopMargin); + break; + + default: + unknownSequence(b); + break; + } + break; + + case 'K': // ESC [ Pn K - Erase in Line + switch (getArg0(0)) { + case 0: // Clear to right + blockClear(mCursorCol, mCursorRow, mColumns - mCursorCol); + break; + + case 1: // Erase start of line to cursor (including cursor) + blockClear(0, mCursorRow, mCursorCol + 1); + break; + + case 2: // Clear whole line + blockClear(0, mCursorRow, mColumns); + break; + + default: + unknownSequence(b); + break; + } + break; + + case 'L': // Insert Lines + { + int linesAfterCursor = mBottomMargin - mCursorRow; + int linesToInsert = Math.min(getArg0(1), linesAfterCursor); + int linesToMove = linesAfterCursor - linesToInsert; + mScreen.blockCopy(0, mCursorRow, mColumns, linesToMove, 0, + mCursorRow + linesToInsert); + blockClear(0, mCursorRow, mColumns, linesToInsert); + } + break; + + case 'M': // Delete Lines + { + int linesAfterCursor = mBottomMargin - mCursorRow; + int linesToDelete = Math.min(getArg0(1), linesAfterCursor); + int linesToMove = linesAfterCursor - linesToDelete; + mScreen.blockCopy(0, mCursorRow + linesToDelete, mColumns, + linesToMove, 0, mCursorRow); + blockClear(0, mCursorRow + linesToMove, mColumns, linesToDelete); + } + break; + + case 'P': // Delete Characters + { + int charsAfterCursor = mColumns - mCursorCol; + int charsToDelete = Math.min(getArg0(1), charsAfterCursor); + int charsToMove = charsAfterCursor - charsToDelete; + mScreen.blockCopy(mCursorCol + charsToDelete, mCursorRow, + charsToMove, 1, mCursorCol, mCursorRow); + blockClear(mCursorCol + charsToMove, mCursorRow, charsToDelete); + } + break; + + case 'T': // Mouse tracking + unimplementedSequence(b); + break; + + case '?': // Esc [ ? -- start of a private mode set + continueSequence(ESC_LEFT_SQUARE_BRACKET_QUESTION_MARK); + break; + + case 'c': // Send device attributes + sendDeviceAttributes(); + break; + + case 'd': // ESC [ Pn d - Vert Position Absolute + setCursorRow(Math.min(Math.max(1, getArg0(1)), mRows) - 1); + break; + + case 'f': // Horizontal and Vertical Position + setHorizontalVerticalPosition(); + break; + + case 'g': // Clear tab stop + switch (getArg0(0)) { + case 0: + mTabStop[mCursorCol] = false; + break; + + case 3: + for (int i = 0; i < mColumns; i++) { + mTabStop[i] = false; + } + break; + + default: + // Specified to have no effect. + break; + } + break; + + case 'h': // Set Mode + doSetMode(true); + break; + + case 'l': // Reset Mode + doSetMode(false); + break; + + case 'm': // Esc [ Pn m - character attributes. + selectGraphicRendition(); + break; + + case 'r': // Esc [ Pn ; Pn r - set top and bottom margins + { + // The top margin defaults to 1, the bottom margin + // (unusually for arguments) defaults to mRows. + // + // The escape sequence numbers top 1..23, but we + // number top 0..22. + // The escape sequence numbers bottom 2..24, and + // so do we (because we use a zero based numbering + // scheme, but we store the first line below the + // bottom-most scrolling line. + // As a result, we adjust the top line by -1, but + // we leave the bottom line alone. + // + // Also require that top + 2 <= bottom + + int top = Math.max(0, Math.min(getArg0(1) - 1, mRows - 2)); + int bottom = Math.max(top + 2, Math.min(getArg1(mRows), mRows)); + mTopMargin = top; + mBottomMargin = bottom; + + // The cursor is placed in the home position + setCursorRowCol(mTopMargin, 0); + } + break; + + default: + parseArg(b); + break; + } + } + + private void selectGraphicRendition() { + for (int i = 0; i <= mArgIndex; i++) { + int code = mArgs[i]; + if ( code < 0) { + if (mArgIndex > 0) { + continue; + } else { + code = 0; + } + } + + // See http://en.wikipedia.org/wiki/ANSI_escape_code#graphics + + if (code == 0) { // reset + mInverseColors = false; + mForeColor = 7; + mBackColor = 0; + } else if (code == 1) { // bold + mForeColor |= 0x8; + } else if (code == 4) { // underscore + mBackColor |= 0x8; + } else if (code == 7) { // inverse + mInverseColors = true; + } else if (code == 22) { // Normal color or intensity, neither bright, bold nor faint + mForeColor &= 0x7; + } else if (code == 24) { // underline: none + mBackColor &= 0x7; + } else if (code == 27) { // image: positive + mInverseColors = false; + } else if (code >= 30 && code <= 37) { // foreground color + mForeColor = (mForeColor & 0x8) | (code - 30); + } else if (code == 39) { // set default text color + mForeColor = 7; + mBackColor = mBackColor & 0x7; + } else if (code >= 40 && code <= 47) { // background color + mBackColor = (mBackColor & 0x8) | (code - 40); + } else if (code == 49) { // set default background color + mBackColor = mBackColor & 0x8; // color 0, but preserve underscore. + } else { + if (TermDebug.LOG_UNKNOWN_ESCAPE_SEQUENCES) { + Log.w(TermDebug.LOG_TAG, String.format("SGR unknown code %d", code)); + } + } + } + } + + private void blockClear(int sx, int sy, int w) { + blockClear(sx, sy, w, 1); + } + + private void blockClear(int sx, int sy, int w, int h) { + mScreen.blockSet(sx, sy, w, h, ' ', getForeColor(), getBackColor()); + } + + private int getForeColor() { + return mInverseColors ? + ((mBackColor & 0x7) | (mForeColor & 0x8)) : mForeColor; + } + + private int getBackColor() { + return mInverseColors ? + ((mForeColor & 0x7) | (mBackColor & 0x8)) : mBackColor; + } + + private void doSetMode(boolean newValue) { + int modeBit = getArg0(0); + switch (modeBit) { + case 4: + mInsertMode = newValue; + break; + + case 20: + mAutomaticNewlineMode = newValue; + break; + + default: + unknownParameter(modeBit); + break; + } + } + + private void setHorizontalVerticalPosition() { + + // Parameters are Row ; Column + + setCursorPosition(getArg1(1) - 1, getArg0(1) - 1); + } + + private void setCursorPosition(int x, int y) { + int effectiveTopMargin = 0; + int effectiveBottomMargin = mRows; + if ((mDecFlags & K_ORIGIN_MODE_MASK) != 0) { + effectiveTopMargin = mTopMargin; + effectiveBottomMargin = mBottomMargin; + } + int newRow = + Math.max(effectiveTopMargin, Math.min(effectiveTopMargin + y, + effectiveBottomMargin - 1)); + int newCol = Math.max(0, Math.min(x, mColumns - 1)); + setCursorRowCol(newRow, newCol); + } + + private void sendDeviceAttributes() { + // This identifies us as a DEC vt100 with advanced + // video options. This is what the xterm terminal + // emulator sends. + byte[] attributes = + { + /* VT100 */ + (byte) 27, (byte) '[', (byte) '?', (byte) '1', + (byte) ';', (byte) '2', (byte) 'c' + + /* VT220 + (byte) 27, (byte) '[', (byte) '?', (byte) '6', + (byte) '0', (byte) ';', + (byte) '1', (byte) ';', + (byte) '2', (byte) ';', + (byte) '6', (byte) ';', + (byte) '8', (byte) ';', + (byte) '9', (byte) ';', + (byte) '1', (byte) '5', (byte) ';', + (byte) 'c' + */ + }; + + write(attributes); + } + + /** + * Send data to the shell process + * @param data + */ + private void write(byte[] data) { + try { + mTermOut.write(data); + mTermOut.flush(); + } catch (IOException e) { + // Ignore exception + // We don't really care if the receiver isn't listening. + // We just make a best effort to answer the query. + } + } + + private void scroll() { + //System.out.println("Scroll(): mTopMargin " + mTopMargin + " mBottomMargin " + mBottomMargin); + mScrollCounter ++; + mScreen.scroll(mTopMargin, mBottomMargin, + getForeColor(), getBackColor()); + } + + /** + * Process the next ASCII character of a parameter. + * + * @param b The next ASCII character of the paramater sequence. + */ + private void parseArg(byte b) { + if (b >= '0' && b <= '9') { + if (mArgIndex < mArgs.length) { + int oldValue = mArgs[mArgIndex]; + int thisDigit = b - '0'; + int value; + if (oldValue >= 0) { + value = oldValue * 10 + thisDigit; + } else { + value = thisDigit; + } + mArgs[mArgIndex] = value; + } + continueSequence(); + } else if (b == ';') { + if (mArgIndex < mArgs.length) { + mArgIndex++; + } + continueSequence(); + } else { + unknownSequence(b); + } + } + + private int getArg0(int defaultValue) { + return getArg(0, defaultValue); + } + + private int getArg1(int defaultValue) { + return getArg(1, defaultValue); + } + + private int getArg(int index, int defaultValue) { + int result = mArgs[index]; + if (result < 0) { + result = defaultValue; + } + return result; + } + + private void unimplementedSequence(byte b) { + if (TermDebug.LOG_UNKNOWN_ESCAPE_SEQUENCES) { + logError("unimplemented", b); + } + finishSequence(); + } + + private void unknownSequence(byte b) { + if (TermDebug.LOG_UNKNOWN_ESCAPE_SEQUENCES) { + logError("unknown", b); + } + finishSequence(); + } + + private void unknownParameter(int parameter) { + if (TermDebug.LOG_UNKNOWN_ESCAPE_SEQUENCES) { + StringBuilder buf = new StringBuilder(); + buf.append("Unknown parameter"); + buf.append(parameter); + logError(buf.toString()); + } + } + + private void logError(String errorType, byte b) { + if (TermDebug.LOG_UNKNOWN_ESCAPE_SEQUENCES) { + StringBuilder buf = new StringBuilder(); + buf.append(errorType); + buf.append(" sequence "); + buf.append(" EscapeState: "); + buf.append(mEscapeState); + buf.append(" char: '"); + buf.append((char) b); + buf.append("' ("); + buf.append(b); + buf.append(")"); + boolean firstArg = true; + for (int i = 0; i <= mArgIndex; i++) { + int value = mArgs[i]; + if (value >= 0) { + if (firstArg) { + firstArg = false; + buf.append("args = "); + } + buf.append(String.format("%d; ", value)); + } + } + logError(buf.toString()); + } + } + + private void logError(String error) { + if (TermDebug.LOG_UNKNOWN_ESCAPE_SEQUENCES) { + Log.e(TermDebug.LOG_TAG, error); + } + finishSequence(); + } + + private void finishSequence() { + mEscapeState = ESC_NONE; + } + + private boolean autoWrapEnabled() { + // Always enable auto wrap, because it's useful on a small screen + return true; + // return (mDecFlags & K_WRAPAROUND_MODE_MASK) != 0; + } + + /** + * Send an ASCII character to the screen. + * + * @param b the ASCII character to display. + */ + private void emit(byte b) { + boolean autoWrap = autoWrapEnabled(); + + if (autoWrap) { + if (mCursorCol == mColumns - 1 && mAboutToAutoWrap) { + mScreen.setLineWrap(mCursorRow); + mCursorCol = 0; + if (mCursorRow + 1 < mBottomMargin) { + mCursorRow++; + } else { + scroll(); + } + } + } + + if (mInsertMode) { // Move character to right one space + int destCol = mCursorCol + 1; + if (destCol < mColumns) { + mScreen.blockCopy(mCursorCol, mCursorRow, mColumns - destCol, + 1, destCol, mCursorRow); + } + } + + mScreen.set(mCursorCol, mCursorRow, b, getForeColor(), getBackColor()); + + if (autoWrap) { + mAboutToAutoWrap = (mCursorCol == mColumns - 1); + } + + mCursorCol = Math.min(mCursorCol + 1, mColumns - 1); + } + + private void setCursorRow(int row) { + mCursorRow = row; + mAboutToAutoWrap = false; + } + + private void setCursorCol(int col) { + mCursorCol = col; + mAboutToAutoWrap = false; + } + + private void setCursorRowCol(int row, int col) { + mCursorRow = Math.min(row, mRows-1); + mCursorCol = Math.min(col, mColumns-1); + mAboutToAutoWrap = false; + } + + public int getScrollCounter() { + return mScrollCounter; + } + + public void clearScrollCounter() { + mScrollCounter = 0; + } + + /** + * Reset the terminal emulator to its initial state. + */ + public void reset() { + mCursorRow = 0; + mCursorCol = 0; + mArgIndex = 0; + mContinueSequence = false; + mEscapeState = ESC_NONE; + mSavedCursorRow = 0; + mSavedCursorCol = 0; + mDecFlags = 0; + mSavedDecFlags = 0; + mInsertMode = false; + mAutomaticNewlineMode = false; + mTopMargin = 0; + mBottomMargin = mRows; + mAboutToAutoWrap = false; + mForeColor = 7; + mBackColor = 0; + mInverseColors = false; + mbKeypadApplicationMode = false; + mAlternateCharSet = false; + // mProcessedCharCount is preserved unchanged. + setDefaultTabStops(); + blockClear(0, 0, mColumns, mRows); + } + + public String getSelectedText(int x1, int y1, int x2, int y2) { + return mScreen.getSelectedText(x1, y1, x2, y2); + } +} diff --git a/src/jackpal/androidterm/session/TranscriptScreen.java b/src/jackpal/androidterm/session/TranscriptScreen.java new file mode 100644 index 000000000..9d3f37a88 --- /dev/null +++ b/src/jackpal/androidterm/session/TranscriptScreen.java @@ -0,0 +1,477 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jackpal.androidterm2.session; + +import android.graphics.Canvas; +import android.util.Log; + +import jackpal.androidterm2.model.Screen; +import jackpal.androidterm2.model.TextRenderer; + +/** + * A TranscriptScreen is a screen that remembers data that's been scrolled. The + * old data is stored in a ring buffer to minimize the amount of copying that + * needs to be done. The transcript does its own drawing, to avoid having to + * expose its internal data structures. + */ +public class TranscriptScreen implements Screen { + private static final String TAG = "TranscriptScreen"; + + /** + * The width of the transcript, in characters. Fixed at initialization. + */ + private int mColumns; + + /** + * The total number of rows in the transcript and the screen. Fixed at + * initialization. + */ + private int mTotalRows; + + /** + * The number of rows in the active portion of the transcript. Doesn't + * include the screen. + */ + private int mActiveTranscriptRows; + + /** + * Which row is currently the topmost line of the transcript. Used to + * implement a circular buffer. + */ + private int mHead; + + /** + * The number of active rows, includes both the transcript and the screen. + */ + private int mActiveRows; + + /** + * The number of rows in the screen. + */ + private int mScreenRows; + + /** + * The data for both the screen and the transcript. The first mScreenRows * + * mLineWidth characters are the screen, the rest are the transcript. + * The low byte encodes the ASCII character, the high byte encodes the + * foreground and background colors, plus underline and bold. + */ + private char[] mData; + + /** + * The data's stored as color-encoded chars, but the drawing routines require chars, so we + * need a temporary buffer to hold a row's worth of characters. + */ + private char[] mRowBuffer; + + /** + * Flags that keep track of whether the current line logically wraps to the + * next line. This is used when resizing the screen and when copying to the + * clipboard or an email attachment + */ + + private boolean[] mLineWrap; + + /** + * Create a transcript screen. + * + * @param columns the width of the screen in characters. + * @param totalRows the height of the entire text area, in rows of text. + * @param screenRows the height of just the screen, not including the + * transcript that holds lines that have scrolled off the top of the + * screen. + */ + public TranscriptScreen(int columns, int totalRows, int screenRows, + int foreColor, int backColor) { + init(columns, totalRows, screenRows, foreColor, backColor); + } + + private void init(int columns, int totalRows, int screenRows, int foreColor, int backColor) { + mColumns = columns; + mTotalRows = totalRows; + mActiveTranscriptRows = 0; + mHead = 0; + mActiveRows = screenRows; + mScreenRows = screenRows; + int totalSize = columns * totalRows; + mData = new char[totalSize]; + blockSet(0, 0, mColumns, mScreenRows, ' ', foreColor, backColor); + mRowBuffer = new char[columns]; + mLineWrap = new boolean[totalRows]; + consistencyCheck(); + } + + public void finish() { + /* + * The Android InputMethodService will sometimes hold a reference to + * us for a while after the activity closes, which is expensive because + * it means holding on to the now-useless mData array. Explicitly + * get rid of our references to this data to help keep the amount of + * memory being leaked down. + */ + mData = null; + mRowBuffer = null; + mLineWrap = null; + } + + /** + * Convert a row value from the public external coordinate system to our + * internal private coordinate system. External coordinate system: + * -mActiveTranscriptRows to mScreenRows-1, with the screen being + * 0..mScreenRows-1 Internal coordinate system: 0..mScreenRows-1 rows of + * mData are the visible rows. mScreenRows..mActiveRows - 1 are the + * transcript, stored as a circular buffer. + * + * @param row a row in the external coordinate system. + * @return The row corresponding to the input argument in the private + * coordinate system. + */ + private int externalToInternalRow(int row) { + if (row < -mActiveTranscriptRows || row >= mScreenRows) { + String errorMessage = "externalToInternalRow "+ row + + " " + mActiveTranscriptRows + " " + mScreenRows; + Log.e(TAG, errorMessage); + throw new IllegalArgumentException(errorMessage); + } + if (row >= 0) { + return row; // This is a visible row. + } + return mScreenRows + + ((mHead + mActiveTranscriptRows + row) % mActiveTranscriptRows); + } + + private int getOffset(int externalLine) { + return externalToInternalRow(externalLine) * mColumns; + } + + private int getOffset(int x, int y) { + return getOffset(y) + x; + } + + public void setLineWrap(int row) { + mLineWrap[externalToInternalRow(row)] = true; + } + + /** + * Store byte b into the screen at location (x, y) + * + * @param x X coordinate (also known as column) + * @param y Y coordinate (also known as row) + * @param b ASCII character to store + * @param foreColor the foreground color + * @param backColor the background color + */ + public void set(int x, int y, byte b, int foreColor, int backColor) { + mData[getOffset(x, y)] = encode(b, foreColor, backColor); + } + + private char encode(int b, int foreColor, int backColor) { + return (char) ((foreColor << 12) | (backColor << 8) | b); + } + + /** + * Scroll the screen down one line. To scroll the whole screen of a 24 line + * screen, the arguments would be (0, 24). + * + * @param topMargin First line that is scrolled. + * @param bottomMargin One line after the last line that is scrolled. + */ + public void scroll(int topMargin, int bottomMargin, int foreColor, + int backColor) { + // Separate out reasons so that stack crawls help us + // figure out which condition was violated. + if (topMargin > bottomMargin - 1) { + throw new IllegalArgumentException(); + } + + if (topMargin > mScreenRows - 1) { + throw new IllegalArgumentException(); + } + + if (bottomMargin > mScreenRows) { + throw new IllegalArgumentException(); + } + + // Adjust the transcript so that the last line of the transcript + // is ready to receive the newly scrolled data + consistencyCheck(); + int expansionRows = Math.min(1, mTotalRows - mActiveRows); + int rollRows = 1 - expansionRows; + mActiveRows += expansionRows; + mActiveTranscriptRows += expansionRows; + if (mActiveTranscriptRows > 0) { + mHead = (mHead + rollRows) % mActiveTranscriptRows; + } + consistencyCheck(); + + // Block move the scroll line to the transcript + int topOffset = getOffset(topMargin); + int destOffset = getOffset(-1); + System.arraycopy(mData, topOffset, mData, destOffset, mColumns); + + int topLine = externalToInternalRow(topMargin); + int destLine = externalToInternalRow(-1); + System.arraycopy(mLineWrap, topLine, mLineWrap, destLine, 1); + + // Block move the scrolled data up + int numScrollChars = (bottomMargin - topMargin - 1) * mColumns; + System.arraycopy(mData, topOffset + mColumns, mData, topOffset, + numScrollChars); + int numScrollLines = (bottomMargin - topMargin - 1); + System.arraycopy(mLineWrap, topLine + 1, mLineWrap, topLine, + numScrollLines); + + // Erase the bottom line of the scroll region + blockSet(0, bottomMargin - 1, mColumns, 1, ' ', foreColor, backColor); + mLineWrap[externalToInternalRow(bottomMargin-1)] = false; + } + + private void consistencyCheck() { + checkPositive(mColumns); + checkPositive(mTotalRows); + checkRange(0, mActiveTranscriptRows, mTotalRows); + if (mActiveTranscriptRows == 0) { + checkEqual(mHead, 0); + } else { + checkRange(0, mHead, mActiveTranscriptRows-1); + } + checkEqual(mScreenRows + mActiveTranscriptRows, mActiveRows); + checkRange(0, mScreenRows, mTotalRows); + + checkEqual(mTotalRows, mLineWrap.length); + checkEqual(mTotalRows*mColumns, mData.length); + checkEqual(mColumns, mRowBuffer.length); + } + + private void checkPositive(int n) { + if (n < 0) { + throw new IllegalArgumentException("checkPositive " + n); + } + } + + private void checkRange(int a, int b, int c) { + if (a > b || b > c) { + throw new IllegalArgumentException("checkRange " + a + " <= " + b + " <= " + c); + } + } + + private void checkEqual(int a, int b) { + if (a != b) { + throw new IllegalArgumentException("checkEqual " + a + " == " + b); + } + } + + /** + * Block copy characters from one position in the screen to another. The two + * positions can overlap. All characters of the source and destination must + * be within the bounds of the screen, or else an InvalidParemeterException + * will be thrown. + * + * @param sx source X coordinate + * @param sy source Y coordinate + * @param w width + * @param h height + * @param dx destination X coordinate + * @param dy destination Y coordinate + */ + public void blockCopy(int sx, int sy, int w, int h, int dx, int dy) { + if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows + || dx < 0 || dx + w > mColumns || dy < 0 + || dy + h > mScreenRows) { + throw new IllegalArgumentException(); + } + if (sy > dy) { + // Move in increasing order + for (int y = 0; y < h; y++) { + int srcOffset = getOffset(sx, sy + y); + int dstOffset = getOffset(dx, dy + y); + System.arraycopy(mData, srcOffset, mData, dstOffset, w); + } + } else { + // Move in decreasing order + for (int y = 0; y < h; y++) { + int y2 = h - (y + 1); + int srcOffset = getOffset(sx, sy + y2); + int dstOffset = getOffset(dx, dy + y2); + System.arraycopy(mData, srcOffset, mData, dstOffset, w); + } + } + } + + /** + * Block set characters. All characters must be within the bounds of the + * screen, or else and InvalidParemeterException will be thrown. Typically + * this is called with a "val" argument of 32 to clear a block of + * characters. + * + * @param sx source X + * @param sy source Y + * @param w width + * @param h height + * @param val value to set. + */ + public void blockSet(int sx, int sy, int w, int h, int val, + int foreColor, int backColor) { + if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows) { + throw new IllegalArgumentException(); + } + char[] data = mData; + char encodedVal = encode(val, foreColor, backColor); + for (int y = 0; y < h; y++) { + int offset = getOffset(sx, sy + y); + for (int x = 0; x < w; x++) { + data[offset + x] = encodedVal; + } + } + } + + /** + * Draw a row of text. Out-of-bounds rows are blank, not errors. + * + * @param row The row of text to draw. + * @param canvas The canvas to draw to. + * @param x The x coordinate origin of the drawing + * @param y The y coordinate origin of the drawing + * @param renderer The renderer to use to draw the text + * @param cx the cursor X coordinate, -1 means don't draw it + * @param selx1 the text selection start X coordinate + * @param selx2 the text selection end X coordinate, if equals to selx1 don't draw selection + * @param imeText current IME text, to be rendered at cursor + */ + public final void drawText(int row, Canvas canvas, float x, float y, + TextRenderer renderer, int cx, int selx1, int selx2, String imeText) { + + // Out-of-bounds rows are blank. + if (row < -mActiveTranscriptRows || row >= mScreenRows) { + return; + } + + // Copy the data from the byte array to a char array so they can + // be drawn. + + int offset = getOffset(row); + char[] rowBuffer = mRowBuffer; + char[] data = mData; + int columns = mColumns; + int lastColors = 0; + int lastRunStart = -1; + final int CURSOR_MASK = 0x10000; + for (int i = 0; i < columns; i++) { + char c = data[offset + i]; + int colors = (char) (c & 0xff00); + if (cx == i || (i >= selx1 && i <= selx2)) { + // Set cursor background color: + colors |= CURSOR_MASK; + } + rowBuffer[i] = (char) (c & 0x00ff); + if (colors != lastColors) { + if (lastRunStart >= 0) { + renderer.drawTextRun(canvas, x, y, lastRunStart, rowBuffer, + lastRunStart, i - lastRunStart, + (lastColors & CURSOR_MASK) != 0, + 0xf & (lastColors >> 12), 0xf & (lastColors >> 8)); + } + lastColors = colors; + lastRunStart = i; + } + } + if (lastRunStart >= 0) { + renderer.drawTextRun(canvas, x, y, lastRunStart, rowBuffer, + lastRunStart, columns - lastRunStart, + (lastColors & CURSOR_MASK) != 0, + 0xf & (lastColors >> 12), 0xf & (lastColors >> 8)); + } + + if (cx >= 0 && imeText.length() > 0) { + int imeLength = Math.min(columns, imeText.length()); + int imeOffset = imeText.length() - imeLength; + int imePosition = Math.min(cx, columns - imeLength); + renderer.drawTextRun(canvas, x, y, imePosition, imeText.toCharArray(), + imeOffset, imeLength, true, 0x0f, 0x00); + } + } + + /** + * Get the count of active rows. + * + * @return the count of active rows. + */ + public int getActiveRows() { + return mActiveRows; + } + + /** + * Get the count of active transcript rows. + * + * @return the count of active transcript rows. + */ + public int getActiveTranscriptRows() { + return mActiveTranscriptRows; + } + + public String getTranscriptText() { + return internalGetTranscriptText(true, 0, -mActiveTranscriptRows, mColumns, mScreenRows); + } + + public String getSelectedText(int selX1, int selY1, int selX2, int selY2) { + return internalGetTranscriptText(true, selX1, selY1, selX2, selY2); + } + + private String internalGetTranscriptText(boolean stripColors, int selX1, int selY1, int selX2, int selY2) { + StringBuilder builder = new StringBuilder(); + char[] rowBuffer = mRowBuffer; + char[] data = mData; + int columns = mColumns; + for (int row = -mActiveTranscriptRows; row < mScreenRows; row++) { + int offset = getOffset(row); + int lastPrintingChar = -1; + for (int column = 0; column < columns; column++) { + char c = data[offset + column]; + if (stripColors) { + c = (char) (c & 0xff); + } + if ((c & 0xff) != ' ') { + lastPrintingChar = column; + } + rowBuffer[column] = c; + } + if ( row >= selY1 && row <= selY2 ) { + int x1 = 0; + int x2 = 0; + if ( row == selY1 ) { + x1 = selX1; + } + if ( row == selY2 ) { + x2 = selX2; + } else { + x2 = columns; + } + if (mLineWrap[externalToInternalRow(row)]) { + builder.append(rowBuffer, x1, x2 - x1); + } else { + builder.append(rowBuffer, x1, Math.max(0, Math.min(x2 - x1 + 1, lastPrintingChar + 1 - x1))); + builder.append('\n'); + } + } + } + return builder.toString(); + } + + public void resize(int columns, int rows, int foreColor, int backColor) { + init(columns, mTotalRows, rows, foreColor, backColor); + } +} diff --git a/src/jackpal/androidterm/util/ByteQueue.java b/src/jackpal/androidterm/util/ByteQueue.java new file mode 100644 index 000000000..ca5199d8d --- /dev/null +++ b/src/jackpal/androidterm/util/ByteQueue.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jackpal.androidterm2.util; + +/** + * A multi-thread-safe produce-consumer byte array. + * Only allows one producer and one consumer. + */ + +public class ByteQueue { + public ByteQueue(int size) { + mBuffer = new byte[size]; + } + + public int getBytesAvailable() { + synchronized(this) { + return mStoredBytes; + } + } + + public int read(byte[] buffer, int offset, int length) + throws InterruptedException { + if (length + offset > buffer.length) { + throw + new IllegalArgumentException("length + offset > buffer.length"); + } + if (length < 0) { + throw + new IllegalArgumentException("length < 0"); + + } + if (length == 0) { + return 0; + } + synchronized(this) { + while (mStoredBytes == 0) { + wait(); + } + int totalRead = 0; + int bufferLength = mBuffer.length; + boolean wasFull = bufferLength == mStoredBytes; + while (length > 0 && mStoredBytes > 0) { + int oneRun = Math.min(bufferLength - mHead, mStoredBytes); + int bytesToCopy = Math.min(length, oneRun); + System.arraycopy(mBuffer, mHead, buffer, offset, bytesToCopy); + mHead += bytesToCopy; + if (mHead >= bufferLength) { + mHead = 0; + } + mStoredBytes -= bytesToCopy; + length -= bytesToCopy; + offset += bytesToCopy; + totalRead += bytesToCopy; + } + if (wasFull) { + notify(); + } + return totalRead; + } + } + + public void write(byte[] buffer, int offset, int length) + throws InterruptedException { + if (length + offset > buffer.length) { + throw + new IllegalArgumentException("length + offset > buffer.length"); + } + if (length < 0) { + throw + new IllegalArgumentException("length < 0"); + + } + if (length == 0) { + return; + } + synchronized(this) { + int bufferLength = mBuffer.length; + boolean wasEmpty = mStoredBytes == 0; + while (length > 0) { + while(bufferLength == mStoredBytes) { + wait(); + } + int tail = mHead + mStoredBytes; + int oneRun; + if (tail >= bufferLength) { + tail = tail - bufferLength; + oneRun = mHead - tail; + } else { + oneRun = bufferLength - tail; + } + int bytesToCopy = Math.min(oneRun, length); + System.arraycopy(buffer, offset, mBuffer, tail, bytesToCopy); + offset += bytesToCopy; + mStoredBytes += bytesToCopy; + length -= bytesToCopy; + } + if (wasEmpty) { + notify(); + } + } + } + + private byte[] mBuffer; + private int mHead; + private int mStoredBytes; +} diff --git a/src/jackpal/androidterm/ServiceForegroundCompat.java b/src/jackpal/androidterm/util/ServiceForegroundCompat.java similarity index 99% rename from src/jackpal/androidterm/ServiceForegroundCompat.java rename to src/jackpal/androidterm/util/ServiceForegroundCompat.java index 549044d0c..d31c86fba 100644 --- a/src/jackpal/androidterm/ServiceForegroundCompat.java +++ b/src/jackpal/androidterm/util/ServiceForegroundCompat.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package jackpal.androidterm2; +package jackpal.androidterm2.util; import java.lang.reflect.Method; import java.lang.reflect.InvocationTargetException; diff --git a/src/jackpal/androidterm/util/TermSettings.java b/src/jackpal/androidterm/util/TermSettings.java new file mode 100644 index 000000000..6df04c3ab --- /dev/null +++ b/src/jackpal/androidterm/util/TermSettings.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jackpal.androidterm2.util; + +import android.content.SharedPreferences; +import android.view.KeyEvent; + +/** + * Terminal emulator settings + */ +public class TermSettings { + private SharedPreferences mPrefs; + + private int mStatusBar = 0; + private int mCursorStyle = 0; + private int mCursorBlink = 0; + private int mFontSize = 9; + private int mColorId = 2; + private int mControlKeyId = 5; // Default to Volume Down + private int mFnKeyId = 4; // Default to Volume Up + private int mUseCookedIME = 0; + private String mShell; + private String mInitialCommand; + + private static final String STATUSBAR_KEY = "statusbar"; + private static final String CURSORSTYLE_KEY = "cursorstyle"; + private static final String CURSORBLINK_KEY = "cursorblink"; + private static final String FONTSIZE_KEY = "fontsize"; + private static final String COLOR_KEY = "color"; + private static final String CONTROLKEY_KEY = "controlkey"; + private static final String FNKEY_KEY = "fnkey"; + private static final String IME_KEY = "ime"; + private static final String SHELL_KEY = "shell"; + private static final String INITIALCOMMAND_KEY = "initialcommand"; + + public static final int WHITE = 0xffffffff; + public static final int BLACK = 0xff000000; + public static final int BLUE = 0xff344ebd; + public static final int GREEN = 0xff00ff00; + public static final int AMBER = 0xffffb651; + public static final int RED = 0xffff0113; + + public static final int[][] COLOR_SCHEMES = {{BLACK, WHITE}, {WHITE, BLACK}, {WHITE, BLUE}, {GREEN, BLACK}, {AMBER, BLACK}, {RED, BLACK}}; + + /** An integer not in the range of real key codes. */ + public static final int KEYCODE_NONE = -1; + + public static final int CONTROL_KEY_ID_NONE = 7; + public static final int[] CONTROL_KEY_SCHEMES = { + KeyEvent.KEYCODE_DPAD_CENTER, + KeyEvent.KEYCODE_AT, + KeyEvent.KEYCODE_ALT_LEFT, + KeyEvent.KEYCODE_ALT_RIGHT, + KeyEvent.KEYCODE_VOLUME_UP, + KeyEvent.KEYCODE_VOLUME_DOWN, + KeyEvent.KEYCODE_CAMERA, + KEYCODE_NONE + }; + + public static final int FN_KEY_ID_NONE = 7; + public static final int[] FN_KEY_SCHEMES = { + KeyEvent.KEYCODE_DPAD_CENTER, + KeyEvent.KEYCODE_AT, + KeyEvent.KEYCODE_ALT_LEFT, + KeyEvent.KEYCODE_ALT_RIGHT, + KeyEvent.KEYCODE_VOLUME_UP, + KeyEvent.KEYCODE_VOLUME_DOWN, + KeyEvent.KEYCODE_CAMERA, + KEYCODE_NONE + }; + + public TermSettings(SharedPreferences prefs) { + readPrefs(prefs); + } + + public void readPrefs(SharedPreferences prefs) { + mPrefs = prefs; + mStatusBar = readIntPref(STATUSBAR_KEY, mStatusBar, 1); + // mCursorStyle = readIntPref(CURSORSTYLE_KEY, mCursorStyle, 2); + // mCursorBlink = readIntPref(CURSORBLINK_KEY, mCursorBlink, 1); + mFontSize = readIntPref(FONTSIZE_KEY, mFontSize, 20); + mColorId = readIntPref(COLOR_KEY, mColorId, COLOR_SCHEMES.length - 1); + mControlKeyId = readIntPref(CONTROLKEY_KEY, mControlKeyId, + CONTROL_KEY_SCHEMES.length - 1); + mFnKeyId = readIntPref(FNKEY_KEY, mFnKeyId, + FN_KEY_SCHEMES.length - 1); + mUseCookedIME = readIntPref(IME_KEY, mUseCookedIME, 1); + mShell = readStringPref(SHELL_KEY, mShell); + mInitialCommand = readStringPref(INITIALCOMMAND_KEY, mInitialCommand); + mPrefs = null; // we leak a Context if we hold on to this + } + + private int readIntPref(String key, int defaultValue, int maxValue) { + int val; + try { + val = Integer.parseInt( + mPrefs.getString(key, Integer.toString(defaultValue))); + } catch (NumberFormatException e) { + val = defaultValue; + } + val = Math.max(0, Math.min(val, maxValue)); + return val; + } + + private String readStringPref(String key, String defaultValue) { + return mPrefs.getString(key, defaultValue); + } + + public boolean showStatusBar() { + return (mStatusBar != 0); + } + + public int getCursorStyle() { + return mCursorStyle; + } + + public int getCursorBlink() { + return mCursorBlink; + } + + public int getFontSize() { + return mFontSize; + } + + public int[] getColorScheme() { + return COLOR_SCHEMES[mColorId]; + } + + public int getControlKeyId() { + return mControlKeyId; + } + + public int getFnKeyId() { + return mFnKeyId; + } + + public int getControlKeyCode() { + return CONTROL_KEY_SCHEMES[mControlKeyId]; + } + + public int getFnKeyCode() { + return FN_KEY_SCHEMES[mFnKeyId]; + } + + public boolean useCookedIME() { + return (mUseCookedIME != 0); + } + + public String getShell() { + return mShell; + } + + public String getInitialCommand() { + return mInitialCommand; + } +} diff --git a/tools/build-release b/tools/build-release index 95ced8247..08ed926b6 100755 --- a/tools/build-release +++ b/tools/build-release @@ -7,4 +7,4 @@ cd ~/code/Android-Terminal-Emulator rm -rf bin obj cd jni -~/code/android-ndk-r5b/ndk-build && cd .. && ant release && mv bin/Term-release.apk bin/Term.apk +~/code/android-ndk-r6b/ndk-build && cd .. && ant release && mv bin/Term-release.apk bin/Term.apk diff --git a/tools/rt b/tools/pushAndRun similarity index 80% rename from tools/rt rename to tools/pushAndRun index d32ff083f..190b58fef 100755 --- a/tools/rt +++ b/tools/pushAndRun @@ -1,4 +1,4 @@ #!/bin/bash # command line build script for installing and running terminal emulator. -adb install -r bin/Term.apk && adb shell am start -n jackpal.androidterm/jackpal.androidterm.Term +adb install -r bin/Term.apk && adb shell am start -n jackpal.androidterm2/jackpal.androidterm2.Term From 2a17953138cf75a6462cd4bed9fe894ea51189ff Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Sat, 14 Jan 2012 04:22:46 -0800 Subject: [PATCH 196/847] Set activity labels for WindowList and TermPreferences For TermPreferences, this prevents the title/action bar from reading "Terminal Emulator", which will be confusing on Honeycomb and ICS once (where the title will be much more prominent once we enable the action bar). This also allows us to remove the setTitle() call in WindowList's onCreate() method. Signed-off-by: Jack Palevich --- AndroidManifest.xml | 6 ++++-- src/jackpal/androidterm/WindowList.java | 2 -- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 849aa66a2..8fd89763c 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -17,8 +17,10 @@ - - + + diff --git a/src/jackpal/androidterm/WindowList.java b/src/jackpal/androidterm/WindowList.java index 83ef074dd..9cc1b60c8 100644 --- a/src/jackpal/androidterm/WindowList.java +++ b/src/jackpal/androidterm/WindowList.java @@ -127,8 +127,6 @@ public void onServiceDisconnected(ComponentName arg0) { public void onCreate(Bundle icicle) { super.onCreate(icicle); - setTitle(R.string.window_list); - ListView listView = getListView(); View newWindow = getLayoutInflater().inflate(R.layout.window_list_new_window, listView, false); listView.addHeaderView(newWindow, null, true); From c40111ffeb3523cede1d76731901b7cb73c7dca0 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Sat, 14 Jan 2012 04:22:53 -0800 Subject: [PATCH 197/847] Take a high-performance WifiLock on platforms where it's available A WIFI_MODE_FULL WifiLock does not prevent Android from enabling power-saving measures which increase latency, decrease throughput, and break multicast, all of which are frequently important to programs running in the terminal which need the WifiLock. Instead, on API 12 or later, take a WIFI_MODE_FULL_HIGH_PERF WifiLock, which keeps the wifi radio in the same state as when the system is awake (if the driver supports it). Signed-off-by: Jack Palevich --- src/jackpal/androidterm/Term.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index 406851b97..88c4e728e 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -91,6 +91,8 @@ public class Term extends Activity implements UpdateCallback { private PowerManager.WakeLock mWakeLock; private WifiManager.WifiLock mWifiLock; + // Available on API 12 and later + private static final int WIFI_MODE_FULL_HIGH_PERF = 3; private boolean mBackKeyPressed; @@ -128,7 +130,11 @@ public void onCreate(Bundle icicle) { PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE); mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TermDebug.LOG_TAG); WifiManager wm = (WifiManager)getSystemService(Context.WIFI_SERVICE); - mWifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL, TermDebug.LOG_TAG); + int wifiLockMode = WifiManager.WIFI_MODE_FULL; + if (AndroidCompat.SDK >= 12) { + wifiLockMode = WIFI_MODE_FULL_HIGH_PERF; + } + mWifiLock = wm.createWifiLock(wifiLockMode, TermDebug.LOG_TAG); updatePrefs(); mAlreadyStarted = true; From b98cf9f0780da1c8d94753e67831e56fae3de596 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Sat, 14 Jan 2012 04:23:00 -0800 Subject: [PATCH 198/847] Let external GestureListener intercept GestureDetector events in EmulatorView For Honeycomb and ICS, we want to be able to make a screen tap bring up the Action Bar in fullscreen mode. We could in principle do this by letting EmulatorView hold an ActionBar object, but it seems more sensible to add hooks into EmulatorView to allow an external GestureListener, and have the activity use those to bring up the Action Bar instead. This also allows us to move the existing swipe-to-change-window code into the activity, eliminating the need to hold a TermViewFlipper in the EmulatorView. Signed-off-by: Jack Palevich --- src/jackpal/androidterm/EmulatorView.java | 63 +++++++++++++++-------- src/jackpal/androidterm/Term.java | 29 ++++++++++- 2 files changed, 69 insertions(+), 23 deletions(-) diff --git a/src/jackpal/androidterm/EmulatorView.java b/src/jackpal/androidterm/EmulatorView.java index 575d4de29..470873761 100644 --- a/src/jackpal/androidterm/EmulatorView.java +++ b/src/jackpal/androidterm/EmulatorView.java @@ -64,7 +64,6 @@ public class EmulatorView extends View implements GestureDetector.OnGestureListe private final boolean LOG_KEY_EVENTS = TermDebug.DEBUG && false; private TermSettings mSettings; - private TermViewFlipper mViewFlipper; /** * We defer some initialization until we have been layed out in the view @@ -180,6 +179,8 @@ public class EmulatorView extends View implements GestureDetector.OnGestureListe private int mSelX2 = -1; private int mSelY2 = -1; + private boolean mIsActive = false; + /** * True if we must poll to discover if the view has changed size. * This is the only known way to detect the view changing size due to @@ -210,6 +211,7 @@ public void run() { }; private GestureDetector mGestureDetector; + private GestureDetector.OnGestureListener mExtGestureListener; private float mScrollRemainder; private TermKeyListener mKeyListener; @@ -237,9 +239,9 @@ public void onUpdate() { } }; - public EmulatorView(Context context, TermSession session, TermViewFlipper viewFlipper, DisplayMetrics metrics) { + public EmulatorView(Context context, TermSession session, DisplayMetrics metrics) { super(context); - commonConstructor(context, session, viewFlipper); + commonConstructor(context, session); setDensity(metrics); } @@ -249,6 +251,7 @@ public void setDensity(DisplayMetrics metrics) { } public void onResume() { + mIsActive = true; updateSize(false); if (mbPollForWindowSizeChange) { mHandler.postDelayed(mCheckSize, SCREEN_CHECK_PERIOD); @@ -265,6 +268,7 @@ public void onPause() { if (mCursorBlink != 0) { mHandler.removeCallbacks(mBlinkCursor); } + mIsActive = false; } public void updatePrefs(TermSettings settings) { @@ -592,7 +596,7 @@ public boolean getKeypadApplicationMode() { return mEmulator.getKeypadApplicationMode(); } - private void commonConstructor(Context context, TermSession session, TermViewFlipper viewFlipper) { + private void commonConstructor(Context context, TermSession session) { mTextRenderer = null; mCursorPaint = new Paint(); mCursorPaint.setARGB(255,128,128,128); @@ -605,12 +609,16 @@ private void commonConstructor(Context context, TermSession session, TermViewFli setFocusable(true); setFocusableInTouchMode(true); - initialize(session, viewFlipper); + initialize(session); session.setUpdateCallback(mUpdateNotify); // XXX We should really be able to fetch this from within TermSession session.setProcessExitMessage(context.getString(R.string.process_exit_message)); } + public void setExtGestureListener(GestureDetector.OnGestureListener listener) { + mExtGestureListener = listener; + } + @Override protected int computeVerticalScrollRange() { return mTranscriptScreen.getActiveRows(); @@ -631,13 +639,11 @@ protected int computeVerticalScrollOffset() { * * @param session The terminal session this view will be displaying */ - private void initialize(TermSession session, TermViewFlipper viewFlipper) { + private void initialize(TermSession session) { mTermSession = session; mTranscriptScreen = session.getTranscriptScreen(); mEmulator = session.getEmulator(); - mViewFlipper = viewFlipper; - mKeyListener = new TermKeyListener(session); mTextSize = 10; mForeground = TermSettings.WHITE; @@ -651,6 +657,14 @@ public TermSession getTermSession() { return mTermSession; } + public int getVisibleWidth() { + return mVisibleWidth; + } + + public int getVisibleHeight() { + return mVisibleHeight; + } + /** * Page the terminal view (scroll it up or down by delta screenfulls.) * @@ -704,15 +718,22 @@ public void setUseCookedIME(boolean useCookedIME) { // Begin GestureDetector.OnGestureListener methods public boolean onSingleTapUp(MotionEvent e) { + if (mExtGestureListener != null && mExtGestureListener.onSingleTapUp(e)) { + return true; + } return true; } public void onLongPress(MotionEvent e) { + // XXX hook into external gesture listener showContextMenu(); } public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { + if (mExtGestureListener != null && mExtGestureListener.onScroll(e1, e2, distanceX, distanceY)) { + return true; + } distanceY += mScrollRemainder; int deltaRows = (int) (distanceY / mCharacterHeight); mScrollRemainder = distanceY - deltaRows * mCharacterHeight; @@ -743,27 +764,25 @@ public boolean onJumpTapUp(MotionEvent e1, MotionEvent e2) { public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { - if (Math.abs(velocityX) > Math.abs(velocityY)) { - // Assume user wanted side to side movement - if (velocityX > 0) { - // Left to right swipe -- previous window - mViewFlipper.showPrevious(); - } else { - // Right to left swipe -- next window - mViewFlipper.showNext(); - } - } else { - // TODO: add animation man's (non animated) fling - mScrollRemainder = 0.0f; - onScroll(e1, e2, 0.1f * velocityX, -0.1f * velocityY); + if (mExtGestureListener != null && mExtGestureListener.onFling(e1, e2, velocityX, velocityY)) { + return true; } + // TODO: add animation man's (non animated) fling + mScrollRemainder = 0.0f; + onScroll(e1, e2, 0.1f * velocityX, -0.1f * velocityY); return true; } public void onShowPress(MotionEvent e) { + if (mExtGestureListener != null) { + mExtGestureListener.onShowPress(e); + } } public boolean onDown(MotionEvent e) { + if (mExtGestureListener != null && mExtGestureListener.onDown(e)) { + return true; + } mScrollRemainder = 0.0f; return true; } @@ -966,7 +985,7 @@ public void updateSize(boolean force) { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); - if (this == mViewFlipper.getCurrentView()) { + if (mIsActive) { updateSize(false); } } diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index 88c4e728e..902876445 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -38,6 +38,7 @@ import android.util.Log; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; +import android.view.GestureDetector.SimpleOnGestureListener; import android.view.Gravity; import android.view.KeyEvent; import android.view.Menu; @@ -110,6 +111,31 @@ public void onServiceDisconnected(ComponentName arg0) { } }; + private class EmulatorViewGestureListener extends SimpleOnGestureListener { + private EmulatorView view; + + public EmulatorViewGestureListener(EmulatorView view) { + this.view = view; + } + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { + if (Math.abs(velocityX) > Math.abs(velocityY)) { + // Assume user wanted side to side movement + if (velocityX > 0) { + // Left to right swipe -- previous window + mViewFlipper.showPrevious(); + } else { + // Right to left swipe -- next window + mViewFlipper.showNext(); + } + return true; + } else { + return false; + } + } + } + @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); @@ -202,7 +228,7 @@ private TermSession createTermSession() { private EmulatorView createEmulatorView(TermSession session) { DisplayMetrics metrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(metrics); - EmulatorView emulatorView = new EmulatorView(this, session, mViewFlipper, metrics); + EmulatorView emulatorView = new EmulatorView(this, session, metrics); FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( FrameLayout.LayoutParams.WRAP_CONTENT, @@ -211,6 +237,7 @@ private EmulatorView createEmulatorView(TermSession session) { ); emulatorView.setLayoutParams(params); + emulatorView.setExtGestureListener(new EmulatorViewGestureListener(emulatorView)); registerForContextMenu(emulatorView); return emulatorView; From b713bf5fb880d31a4dc0c34f7a88fa0a935be223 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Sat, 14 Jan 2012 04:23:22 -0800 Subject: [PATCH 199/847] Make swipe-to-switch-windows less sensitive At the moment, it's too easy to accidentally trigger a window switch while swiping to scroll through a terminal's transcript; fix this by triggering a window switch only when the gesture is clearly intended to be horizontal (V_y > 2V_x). Signed-off-by: Jack Palevich --- src/jackpal/androidterm/Term.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index 902876445..e278c513e 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -120,7 +120,7 @@ public EmulatorViewGestureListener(EmulatorView view) { @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { - if (Math.abs(velocityX) > Math.abs(velocityY)) { + if (Math.abs(velocityX) > 2*Math.abs(velocityY)) { // Assume user wanted side to side movement if (velocityX > 0) { // Left to right swipe -- previous window From bcc49083b4de73c86e632d141a40920978c7a9b1 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Sat, 14 Jan 2012 04:23:30 -0800 Subject: [PATCH 200/847] Bring up IME on single tap if we have no full hardware keyboard Before Honeycomb, it used to be possible to do this by holding down the Menu key, but later devices don't necessarily have menu keys, and ICS doesn't implement this functionality even on devices that do. Instead, give everyone an easier method of bringing up the software keyboard. Signed-off-by: Jack Palevich --- src/jackpal/androidterm/Term.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index e278c513e..53faafcf5 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -111,6 +111,8 @@ public void onServiceDisconnected(ComponentName arg0) { } }; + private boolean mHaveFullHwKeyboard = false; + private class EmulatorViewGestureListener extends SimpleOnGestureListener { private EmulatorView view; @@ -118,6 +120,14 @@ public EmulatorViewGestureListener(EmulatorView view) { this.view = view; } + @Override + public boolean onSingleTapUp(MotionEvent e) { + if (!mHaveFullHwKeyboard) { + doToggleSoftKeyboard(); + } + return true; + } + @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { if (Math.abs(velocityX) > 2*Math.abs(velocityY)) { @@ -162,6 +172,8 @@ public void onCreate(Bundle icicle) { } mWifiLock = wm.createWifiLock(wifiLockMode, TermDebug.LOG_TAG); + mHaveFullHwKeyboard = checkHaveFullHwKeyboard(getResources().getConfiguration()); + updatePrefs(); mAlreadyStarted = true; } @@ -340,10 +352,17 @@ public void run() { }.start(); } + private boolean checkHaveFullHwKeyboard(Configuration c) { + return (c.keyboard == Configuration.KEYBOARD_QWERTY) && + (c.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO); + } + @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); + mHaveFullHwKeyboard = checkHaveFullHwKeyboard(newConfig); + EmulatorView v = (EmulatorView) mViewFlipper.getCurrentView(); if (v != null) { v.updateSize(true); From 5b212b577576bf2e3898434cd3f68c7fd57a00e9 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Sat, 14 Jan 2012 04:23:53 -0800 Subject: [PATCH 201/847] Split AndroidCompat class and move all compatibility code to a package When introducing Honeycomb/ICS support code, we're going to want to create new classes that can be instantiated (e.g. an ActionBarCompat class which wraps android.app.ActionBar). Splitting the AndroidCompat class allows us to instantiate these classes without first creating an AndroidCompat instance. While we're at it, move all the compat code to a separate package, for organizational reasons. Signed-off-by: Jack Palevich --- src/jackpal/androidterm/EmulatorView.java | 2 +- src/jackpal/androidterm/Term.java | 2 +- src/jackpal/androidterm/TermService.java | 2 +- .../compat/AndroidCharacterCompat.java | 33 +++++++++++ .../androidterm/compat/AndroidCompat.java | 19 +++++++ .../ServiceForegroundCompat.java | 2 +- .../androidterm/util/AndroidCompat.java | 55 ------------------- .../androidterm/util/UnicodeTranscript.java | 8 ++- 8 files changed, 61 insertions(+), 62 deletions(-) create mode 100644 src/jackpal/androidterm/compat/AndroidCharacterCompat.java create mode 100644 src/jackpal/androidterm/compat/AndroidCompat.java rename src/jackpal/androidterm/{util => compat}/ServiceForegroundCompat.java (99%) delete mode 100644 src/jackpal/androidterm/util/AndroidCompat.java diff --git a/src/jackpal/androidterm/EmulatorView.java b/src/jackpal/androidterm/EmulatorView.java index 470873761..793f419cb 100644 --- a/src/jackpal/androidterm/EmulatorView.java +++ b/src/jackpal/androidterm/EmulatorView.java @@ -46,12 +46,12 @@ import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; +import jackpal.androidterm.compat.AndroidCompat; import jackpal.androidterm.model.TextRenderer; import jackpal.androidterm.model.UpdateCallback; import jackpal.androidterm.session.TerminalEmulator; import jackpal.androidterm.session.TermSession; import jackpal.androidterm.session.TranscriptScreen; -import jackpal.androidterm.util.AndroidCompat; import jackpal.androidterm.util.TermSettings; /** diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index 53faafcf5..b9da426f9 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -51,9 +51,9 @@ import android.widget.FrameLayout; import android.widget.Toast; +import jackpal.androidterm.compat.AndroidCompat; import jackpal.androidterm.model.UpdateCallback; import jackpal.androidterm.session.TermSession; -import jackpal.androidterm.util.AndroidCompat; import jackpal.androidterm.util.SessionList; import jackpal.androidterm.util.TermSettings; diff --git a/src/jackpal/androidterm/TermService.java b/src/jackpal/androidterm/TermService.java index 048758fdd..dbe3c2129 100644 --- a/src/jackpal/androidterm/TermService.java +++ b/src/jackpal/androidterm/TermService.java @@ -25,10 +25,10 @@ import android.app.NotificationManager; import android.app.PendingIntent; +import jackpal.androidterm.compat.ServiceForegroundCompat; import jackpal.androidterm.model.SessionFinishCallback; import jackpal.androidterm.model.UpdateCallback; import jackpal.androidterm.session.TermSession; -import jackpal.androidterm.util.ServiceForegroundCompat; import jackpal.androidterm.util.SessionList; public class TermService extends Service implements SessionFinishCallback diff --git a/src/jackpal/androidterm/compat/AndroidCharacterCompat.java b/src/jackpal/androidterm/compat/AndroidCharacterCompat.java new file mode 100644 index 000000000..d3f7d844a --- /dev/null +++ b/src/jackpal/androidterm/compat/AndroidCharacterCompat.java @@ -0,0 +1,33 @@ +package jackpal.androidterm.compat; + +import android.text.AndroidCharacter; + +/** + * Definitions related to android.text.AndroidCharacter + */ +public class AndroidCharacterCompat { + public static final int EAST_ASIAN_WIDTH_NEUTRAL = 0; + public static final int EAST_ASIAN_WIDTH_AMBIGUOUS = 1; + public static final int EAST_ASIAN_WIDTH_HALF_WIDTH = 2; + public static final int EAST_ASIAN_WIDTH_FULL_WIDTH = 3; + public static final int EAST_ASIAN_WIDTH_NARROW = 4; + public static final int EAST_ASIAN_WIDTH_WIDE = 5; + + private static class Api8OrLater { + public static void initialize() { + // Does nothing -- call this to force the class to try to load + } + + public static int getEastAsianWidth(char c) { + return AndroidCharacter.getEastAsianWidth(c); + } + } + + public static int getEastAsianWidth(char c) { + if (AndroidCompat.SDK >= 8) { + return Api8OrLater.getEastAsianWidth(c); + } else { + return EAST_ASIAN_WIDTH_NARROW; + } + } +} diff --git a/src/jackpal/androidterm/compat/AndroidCompat.java b/src/jackpal/androidterm/compat/AndroidCompat.java new file mode 100644 index 000000000..e17f1a69b --- /dev/null +++ b/src/jackpal/androidterm/compat/AndroidCompat.java @@ -0,0 +1,19 @@ +package jackpal.androidterm.compat; + +/** + * The classes in this package take advantage of the fact that the VM does + * not attempt to load a class until it's accessed, and the verifier + * does not run until a class is loaded. By keeping the methods which + * are unavailable on older platforms in subclasses which are only ever + * accessed on platforms where they are available, we can preserve + * compatibility with older platforms without resorting to reflection. + * + * See http://developer.android.com/resources/articles/backward-compatibility.html + * and http://android-developers.blogspot.com/2010/07/how-to-have-your-cupcake-and-eat-it-too.html + * for further discussion of this technique. + */ + +public class AndroidCompat { + public final static int SDK = + Integer.valueOf(android.os.Build.VERSION.SDK); +} diff --git a/src/jackpal/androidterm/util/ServiceForegroundCompat.java b/src/jackpal/androidterm/compat/ServiceForegroundCompat.java similarity index 99% rename from src/jackpal/androidterm/util/ServiceForegroundCompat.java rename to src/jackpal/androidterm/compat/ServiceForegroundCompat.java index 93fb979e4..3c7771bdf 100644 --- a/src/jackpal/androidterm/util/ServiceForegroundCompat.java +++ b/src/jackpal/androidterm/compat/ServiceForegroundCompat.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package jackpal.androidterm.util; +package jackpal.androidterm.compat; import java.lang.reflect.Method; import java.lang.reflect.InvocationTargetException; diff --git a/src/jackpal/androidterm/util/AndroidCompat.java b/src/jackpal/androidterm/util/AndroidCompat.java deleted file mode 100644 index 85622d35f..000000000 --- a/src/jackpal/androidterm/util/AndroidCompat.java +++ /dev/null @@ -1,55 +0,0 @@ -package jackpal.androidterm.util; - -import android.text.AndroidCharacter; - -/** - * Provides APIs post Android version 3. - * - */ -public class AndroidCompat { - public final static int SDK = - Integer.valueOf(android.os.Build.VERSION.SDK); - - /** - * The classes here each take advantage of the fact that the VM does - * not attempt to load a class until it's accessed, and the verifier - * does not run until a class is loaded. By keeping the methods which - * are unavailable on older platforms in subclasses which are only ever - * accessed on platforms where they are available, we can preserve - * compatibility with older platforms without resorting to reflection. - * - * See http://developer.android.com/resources/articles/backward-compatibility.html - * and http://android-developers.blogspot.com/2010/07/how-to-have-your-cupcake-and-eat-it-too.html - * for further discussion of this technique. - */ - - /** - * Definitions related to android.text.AndroidCharacter - */ - public static class AndroidCharacterComp { - public static final int EAST_ASIAN_WIDTH_NEUTRAL = 0; - public static final int EAST_ASIAN_WIDTH_AMBIGUOUS = 1; - public static final int EAST_ASIAN_WIDTH_HALF_WIDTH = 2; - public static final int EAST_ASIAN_WIDTH_FULL_WIDTH = 3; - public static final int EAST_ASIAN_WIDTH_NARROW = 4; - public static final int EAST_ASIAN_WIDTH_WIDE = 5; - - private static class Api8OrLater { - public static void initialize() { - // Does nothing -- call this to force the class to try to load - } - - public static int getEastAsianWidth(char c) { - return AndroidCharacter.getEastAsianWidth(c); - } - } - - public static int getEastAsianWidth(char c) { - if (SDK >= 8) { - return Api8OrLater.getEastAsianWidth(c); - } else { - return EAST_ASIAN_WIDTH_NARROW; - } - } - } -} diff --git a/src/jackpal/androidterm/util/UnicodeTranscript.java b/src/jackpal/androidterm/util/UnicodeTranscript.java index d77a23dce..ce7769fab 100644 --- a/src/jackpal/androidterm/util/UnicodeTranscript.java +++ b/src/jackpal/androidterm/util/UnicodeTranscript.java @@ -18,6 +18,8 @@ import android.util.Log; +import jackpal.androidterm.compat.AndroidCharacterCompat; + /** * A backing store for a TranscriptScreen. * @@ -547,9 +549,9 @@ public static int charWidth(int codePoint) { if (Character.charCount(codePoint) == 1) { // Android's getEastAsianWidth() only works for BMP characters - switch (AndroidCompat.AndroidCharacterComp.getEastAsianWidth((char) codePoint)) { - case AndroidCompat.AndroidCharacterComp.EAST_ASIAN_WIDTH_FULL_WIDTH: - case AndroidCompat.AndroidCharacterComp.EAST_ASIAN_WIDTH_WIDE: + switch (AndroidCharacterCompat.getEastAsianWidth((char) codePoint)) { + case AndroidCharacterCompat.EAST_ASIAN_WIDTH_FULL_WIDTH: + case AndroidCharacterCompat.EAST_ASIAN_WIDTH_WIDE: return 2; } } else { From 4a48d6a8e7796bfc43baf9fd00da1028e294fb56 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Sat, 14 Jan 2012 04:23:58 -0800 Subject: [PATCH 202/847] Make the project build with and target API 11 * Update project.properties to build with API 11 * Update AndroidManifest.xml to indicate that we target API 11 * Uncomment API 11+ specific material in EmulatorView * Add compatibility class for Activity methods new in API 11 * Call invalidateOptionsMenu() where necessary Signed-off-by: Jack Palevich --- AndroidManifest.xml | 2 +- project.properties | 2 +- src/jackpal/androidterm/EmulatorView.java | 3 +- src/jackpal/androidterm/Term.java | 3 ++ .../androidterm/compat/ActivityCompat.java | 36 +++++++++++++++++++ 5 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 src/jackpal/androidterm/compat/ActivityCompat.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 8fd89763c..b8db85bab 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -23,6 +23,6 @@ android:label="@string/window_list" /> - + diff --git a/project.properties b/project.properties index f049142c1..d79abae19 100644 --- a/project.properties +++ b/project.properties @@ -8,4 +8,4 @@ # project structure. # Project target. -target=android-10 +target=android-11 diff --git a/src/jackpal/androidterm/EmulatorView.java b/src/jackpal/androidterm/EmulatorView.java index 793f419cb..3ecb6d89b 100644 --- a/src/jackpal/androidterm/EmulatorView.java +++ b/src/jackpal/androidterm/EmulatorView.java @@ -41,6 +41,7 @@ import android.view.View; import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.CompletionInfo; +import android.view.inputmethod.CorrectionInfo; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; @@ -460,14 +461,12 @@ public boolean reportFullscreenMode(boolean arg0) { return true; } - /** API Level 11, we are currently at API level 10, so don't use this. public boolean commitCorrection (CorrectionInfo correctionInfo) { if (TermDebug.LOG_IME) { Log.w(TAG, "commitCorrection"); } return true; } - */ public boolean commitText(CharSequence text, int newCursorPosition) { if (TermDebug.LOG_IME) { diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index b9da426f9..ec7e42992 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -51,6 +51,7 @@ import android.widget.FrameLayout; import android.widget.Toast; +import jackpal.androidterm.compat.ActivityCompat; import jackpal.androidterm.compat.AndroidCompat; import jackpal.androidterm.model.UpdateCallback; import jackpal.androidterm.session.TermSession; @@ -673,6 +674,7 @@ private void doToggleWakeLock() { } else { mWakeLock.acquire(); } + ActivityCompat.invalidateOptionsMenu(this); } private void doToggleWifiLock() { @@ -681,5 +683,6 @@ private void doToggleWifiLock() { } else { mWifiLock.acquire(); } + ActivityCompat.invalidateOptionsMenu(this); } } diff --git a/src/jackpal/androidterm/compat/ActivityCompat.java b/src/jackpal/androidterm/compat/ActivityCompat.java new file mode 100644 index 000000000..97c3fccdd --- /dev/null +++ b/src/jackpal/androidterm/compat/ActivityCompat.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2011 Steven Luo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jackpal.androidterm.compat; + +import android.app.Activity; + +/** + * Compatibility class for android.app.Activity + */ +public class ActivityCompat { + private static class Api11OrLater { + public static void invalidateOptionsMenu(Activity activity) { + activity.invalidateOptionsMenu(); + } + } + + public static void invalidateOptionsMenu(Activity activity) { + if (AndroidCompat.SDK >= 11) { + Api11OrLater.invalidateOptionsMenu(activity); + } + } +} From 30df452167e75c285925461672da6e91a2955101 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Sat, 14 Jan 2012 04:24:02 -0800 Subject: [PATCH 203/847] Add support for the Action Bar on Honeycomb and later In this incarnation, the action bar is always visible, and no navigation (window title or window switching) is yet provided. We continue to offer the traditional menu on older platforms (no action bar implementation for API < 11 is provided). Signed-off-by: Jack Palevich --- res/values/styles.xml | 4 + src/jackpal/androidterm/EmulatorView.java | 14 ++ src/jackpal/androidterm/Term.java | 23 +++ .../androidterm/compat/ActionBarCompat.java | 146 ++++++++++++++++++ .../androidterm/compat/ActivityCompat.java | 11 ++ .../androidterm/compat/MenuItemCompat.java | 25 +++ 6 files changed, 223 insertions(+) create mode 100644 src/jackpal/androidterm/compat/ActionBarCompat.java create mode 100644 src/jackpal/androidterm/compat/MenuItemCompat.java diff --git a/res/values/styles.xml b/res/values/styles.xml index fe5892c40..5ad5914ea 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -20,5 +20,9 @@ @null @null + diff --git a/src/jackpal/androidterm/EmulatorView.java b/src/jackpal/androidterm/EmulatorView.java index 3ecb6d89b..910b30d59 100644 --- a/src/jackpal/androidterm/EmulatorView.java +++ b/src/jackpal/androidterm/EmulatorView.java @@ -215,9 +215,15 @@ public void run() { private GestureDetector.OnGestureListener mExtGestureListener; private float mScrollRemainder; private TermKeyListener mKeyListener; + private WindowSizeCallback mSizeCallback; private String mImeBuffer = ""; + // Used by activity to inform us how much of the window belongs to us + public interface WindowSizeCallback { + public abstract void onGetSize(Rect rect); + } + /** * Our message handler class. Implements a periodic callback. */ @@ -614,6 +620,10 @@ private void commonConstructor(Context context, TermSession session) { session.setProcessExitMessage(context.getString(R.string.process_exit_message)); } + public void setWindowSizeCallback(WindowSizeCallback callback) { + mSizeCallback = callback; + } + public void setExtGestureListener(GestureDetector.OnGestureListener listener) { mExtGestureListener = listener; } @@ -966,6 +976,10 @@ public void updateSize(boolean force) { mVisibleRect.top = 0; } } + if (mSizeCallback != null) { + // Let activity adjust our size + mSizeCallback.onGetSize(mVisibleRect); + } int w = mVisibleRect.width(); int h = mVisibleRect.height(); // Log.w("Term", "(" + w + ", " + h + ")"); diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index ec7e42992..dc84d35d1 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -27,6 +27,7 @@ import android.content.ServiceConnection; import android.content.res.Configuration; import android.content.res.Resources; +import android.graphics.Rect; import android.net.Uri; import android.net.wifi.WifiManager; import android.os.Bundle; @@ -51,8 +52,10 @@ import android.widget.FrameLayout; import android.widget.Toast; +import jackpal.androidterm.compat.ActionBarCompat; import jackpal.androidterm.compat.ActivityCompat; import jackpal.androidterm.compat.AndroidCompat; +import jackpal.androidterm.compat.MenuItemCompat; import jackpal.androidterm.model.UpdateCallback; import jackpal.androidterm.session.TermSession; import jackpal.androidterm.util.SessionList; @@ -112,6 +115,8 @@ public void onServiceDisconnected(ComponentName arg0) { } }; + private ActionBarCompat mActionBar; + private boolean mHaveFullHwKeyboard = false; private class EmulatorViewGestureListener extends SimpleOnGestureListener { @@ -147,6 +152,15 @@ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float ve } } + private EmulatorView.WindowSizeCallback mSizeCallback = new EmulatorView.WindowSizeCallback() { + public void onGetSize(Rect rect) { + if (mActionBar != null) { + // Fixed action bar takes space away from the EmulatorView + rect.top += mActionBar.getHeight(); + } + } + }; + @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); @@ -161,6 +175,10 @@ public void onCreate(Bundle icicle) { Log.w(TermDebug.LOG_TAG, "bind to service failed!"); } + if (AndroidCompat.SDK >= 11) { + setTheme(R.style.Theme_Holo); + } + setContentView(R.layout.term_activity); mViewFlipper = (TermViewFlipper) findViewById(VIEW_FLIPPER); @@ -173,6 +191,8 @@ public void onCreate(Bundle icicle) { } mWifiLock = wm.createWifiLock(wifiLockMode, TermDebug.LOG_TAG); + mActionBar = ActivityCompat.getActionBar(this); + mHaveFullHwKeyboard = checkHaveFullHwKeyboard(getResources().getConfiguration()); updatePrefs(); @@ -251,6 +271,7 @@ private EmulatorView createEmulatorView(TermSession session) { emulatorView.setLayoutParams(params); emulatorView.setExtGestureListener(new EmulatorViewGestureListener(emulatorView)); + emulatorView.setWindowSizeCallback(mSizeCallback); registerForContextMenu(emulatorView); return emulatorView; @@ -373,6 +394,8 @@ public void onConfigurationChanged(Configuration newConfig) { @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); + MenuItemCompat.setShowAsAction(menu.findItem(R.id.menu_new_window), MenuItemCompat.SHOW_AS_ACTION_ALWAYS); + MenuItemCompat.setShowAsAction(menu.findItem(R.id.menu_close_window), MenuItemCompat.SHOW_AS_ACTION_IF_ROOM); return true; } diff --git a/src/jackpal/androidterm/compat/ActionBarCompat.java b/src/jackpal/androidterm/compat/ActionBarCompat.java new file mode 100644 index 000000000..7a356f43a --- /dev/null +++ b/src/jackpal/androidterm/compat/ActionBarCompat.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2011 Steven Luo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jackpal.androidterm.compat; + +import android.app.ActionBar; +import android.widget.SpinnerAdapter; + +/** + * Provides ActionBar APIs. + */ +public abstract class ActionBarCompat { + public static final int NAVIGATION_MODE_STANDARD = 0; + public static final int NAVIGATION_MODE_LIST = 1; + public static final int NAVIGATION_MODE_TABS = 2; + public static final int DISPLAY_USE_LOGO = 1; + public static final int DISPLAY_SHOW_HOME = 2; + public static final int DISPLAY_HOME_AS_UP = 4; + public static final int DISPLAY_SHOW_TITLE = 8; + public static final int DISPLAY_SHOW_CUSTOM = 16; + + + public interface OnNavigationListener { + public abstract boolean onNavigationItemSelected(int position, long id); + } + + public static ActionBarCompat wrap(Object actionBar) { + if (actionBar != null) { + if (AndroidCompat.SDK >= 11) { + return new ActionBarApi11OrLater(actionBar); + } + } + return null; + } + + public abstract int getDisplayOptions(); + public abstract int getHeight(); + public abstract int getNavigationItemCount(); + public abstract int getNavigationMode(); + public abstract int getSelectedNavigationIndex(); + public abstract CharSequence getTitle(); + public abstract void hide(); + public abstract boolean isShowing(); + public abstract void setDisplayOptions(int options); + public abstract void setDisplayOptions(int options, int mask); + public abstract void setListNavigationCallbacks(SpinnerAdapter adapter, OnNavigationListener callback); + public abstract void setNavigationMode(int mode); + public abstract void setSelectedNavigationItem(int position); + public abstract void setTitle(int resId); + public abstract void setTitle(CharSequence title); + public abstract void show(); +} + +class ActionBarApi11OrLater extends ActionBarCompat { + private ActionBar bar; + + ActionBarApi11OrLater(Object bar) { + this.bar = (ActionBar) bar; + } + + private ActionBar.OnNavigationListener wrapOnNavigationCallback(OnNavigationListener callback) { + final OnNavigationListener cb = callback; + return new ActionBar.OnNavigationListener() { + public boolean onNavigationItemSelected(int position, long id) { + return cb.onNavigationItemSelected(position, id); + } + }; + } + + public int getDisplayOptions() { + return bar.getDisplayOptions(); + } + + public int getHeight() { + return bar.getHeight(); + } + + public int getNavigationItemCount() { + return bar.getNavigationItemCount(); + } + + public int getNavigationMode() { + return bar.getNavigationMode(); + } + + public int getSelectedNavigationIndex() { + return bar.getSelectedNavigationIndex(); + } + + public CharSequence getTitle() { + return bar.getTitle(); + } + + public void hide() { + bar.hide(); + } + + public boolean isShowing() { + return bar.isShowing(); + } + + public void setDisplayOptions(int options) { + bar.setDisplayOptions(options); + } + + public void setDisplayOptions(int options, int mask) { + bar.setDisplayOptions(options, mask); + } + + public void setListNavigationCallbacks(SpinnerAdapter adapter, OnNavigationListener callback) { + bar.setListNavigationCallbacks(adapter, wrapOnNavigationCallback(callback)); + } + + public void setNavigationMode(int mode) { + bar.setNavigationMode(mode); + } + + public void setSelectedNavigationItem(int position) { + bar.setSelectedNavigationItem(position); + } + + public void setTitle(int resId) { + bar.setTitle(resId); + } + + public void setTitle(CharSequence title) { + bar.setTitle(title); + } + + public void show() { + bar.show(); + } +} diff --git a/src/jackpal/androidterm/compat/ActivityCompat.java b/src/jackpal/androidterm/compat/ActivityCompat.java index 97c3fccdd..ab6e4f269 100644 --- a/src/jackpal/androidterm/compat/ActivityCompat.java +++ b/src/jackpal/androidterm/compat/ActivityCompat.java @@ -26,6 +26,10 @@ private static class Api11OrLater { public static void invalidateOptionsMenu(Activity activity) { activity.invalidateOptionsMenu(); } + + public static Object getActionBar(Activity activity) { + return activity.getActionBar(); + } } public static void invalidateOptionsMenu(Activity activity) { @@ -33,4 +37,11 @@ public static void invalidateOptionsMenu(Activity activity) { Api11OrLater.invalidateOptionsMenu(activity); } } + + public static ActionBarCompat getActionBar(Activity activity) { + if (AndroidCompat.SDK < 11) { + return null; + } + return ActionBarCompat.wrap(Api11OrLater.getActionBar(activity)); + } } diff --git a/src/jackpal/androidterm/compat/MenuItemCompat.java b/src/jackpal/androidterm/compat/MenuItemCompat.java new file mode 100644 index 000000000..456fa798b --- /dev/null +++ b/src/jackpal/androidterm/compat/MenuItemCompat.java @@ -0,0 +1,25 @@ +package jackpal.androidterm.compat; + +import android.view.MenuItem; + +/** + * Definitions related to android.view.MenuItem + */ +public class MenuItemCompat { + public static final int SHOW_AS_ACTION_NEVER = 0; + public static final int SHOW_AS_ACTION_IF_ROOM = 1; + public static final int SHOW_AS_ACTION_ALWAYS = 2; + public static final int SHOW_AS_ACTION_WITH_TEXT = 4; + + private static class Api11OrLater { + public static void setShowAsAction(MenuItem item, int actionEnum) { + item.setShowAsAction(actionEnum); + } + } + + public static void setShowAsAction(MenuItem item, int actionEnum) { + if (AndroidCompat.SDK >= 11) { + Api11OrLater.setShowAsAction(item, actionEnum); + } + } +} From 9e55696c5b2fd6154e1f0b620ba7155c068a02dd Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Sat, 14 Jan 2012 04:24:08 -0800 Subject: [PATCH 204/847] Split WindowListAdapter from WindowList We want to reuse this in the action bar. Signed-off-by: Jack Palevich --- src/jackpal/androidterm/WindowList.java | 48 ++--------- .../androidterm/WindowListAdapter.java | 80 +++++++++++++++++++ 2 files changed, 85 insertions(+), 43 deletions(-) create mode 100644 src/jackpal/androidterm/WindowListAdapter.java diff --git a/src/jackpal/androidterm/WindowList.java b/src/jackpal/androidterm/WindowList.java index 9cc1b60c8..0731c7376 100644 --- a/src/jackpal/androidterm/WindowList.java +++ b/src/jackpal/androidterm/WindowList.java @@ -42,47 +42,6 @@ public class WindowList extends ListActivity { private WindowListAdapter mWindowListAdapter; private TermService mTermService; - class WindowListAdapter extends BaseAdapter implements UpdateCallback { - private LayoutInflater inflater = getLayoutInflater(); - - public int getCount() { - return sessions.size(); - } - - public Object getItem(int position) { - return sessions.get(position); - } - - public long getItemId(int position) { - return position; - } - - public View getView(int position, View convertView, ViewGroup parent) { - View child = inflater.inflate(R.layout.window_list_item, parent, false); - TextView label = (TextView) child.findViewById(R.id.window_list_label); - label.setText(getString(R.string.window_title, position + 1)); - - View close = child.findViewById(R.id.window_list_close); - final TermService service = mTermService; - final int closePosition = position; - close.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - TermSession session = service.getSessions().remove(closePosition); - if (session != null) { - session.finish(); - notifyDataSetChanged(); - } - } - }); - - return child; - } - - public void onUpdate() { - notifyDataSetChanged(); - } - } - /** * View which isn't automatically in the pressed state if its parent is * pressed. This allows the window's entry to be pressed without the close @@ -151,6 +110,9 @@ protected void onPause() { if (sessions != null) { sessions.removeCallback(mWindowListAdapter); } + if (mWindowListAdapter != null) { + mWindowListAdapter.setSessions(null); + } unbindService(mTSConnection); } @@ -158,11 +120,11 @@ private void populateList() { sessions = mTermService.getSessions(); if (mWindowListAdapter == null) { - WindowListAdapter adapter = new WindowListAdapter(); + WindowListAdapter adapter = new WindowListAdapter(sessions); setListAdapter(adapter); mWindowListAdapter = adapter; } else { - mWindowListAdapter.notifyDataSetChanged(); + mWindowListAdapter.setSessions(sessions); } sessions.addCallback(mWindowListAdapter); } diff --git a/src/jackpal/androidterm/WindowListAdapter.java b/src/jackpal/androidterm/WindowListAdapter.java new file mode 100644 index 000000000..c8f81a395 --- /dev/null +++ b/src/jackpal/androidterm/WindowListAdapter.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2011 Steven Luo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jackpal.androidterm; + +import android.app.Activity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.TextView; + +import jackpal.androidterm.model.UpdateCallback; +import jackpal.androidterm.session.TermSession; +import jackpal.androidterm.util.SessionList; + +public class WindowListAdapter extends BaseAdapter implements UpdateCallback { + private SessionList mSessions; + + public WindowListAdapter(SessionList sessions) { + setSessions(sessions); + } + + public void setSessions(SessionList sessions) { + mSessions = sessions; + if (sessions != null) { + onUpdate(); + } + } + + public int getCount() { + return mSessions.size(); + } + + public Object getItem(int position) { + return mSessions.get(position); + } + + public long getItemId(int position) { + return position; + } + + public View getView(int position, View convertView, ViewGroup parent) { + Activity act = (Activity) parent.getContext(); + View child = act.getLayoutInflater().inflate(R.layout.window_list_item, parent, false); + TextView label = (TextView) child.findViewById(R.id.window_list_label); + label.setText(act.getString(R.string.window_title, position + 1)); + + View close = child.findViewById(R.id.window_list_close); + final SessionList sessions = mSessions; + final int closePosition = position; + close.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + TermSession session = sessions.remove(closePosition); + if (session != null) { + session.finish(); + notifyDataSetChanged(); + } + } + }); + + return child; + } + + public void onUpdate() { + notifyDataSetChanged(); + } +} From 7531b0189b133f0ccae647115e76210cf312b58e Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Sat, 14 Jan 2012 04:24:13 -0800 Subject: [PATCH 205/847] Add drop-down navigation between sessions to action bar Replace the static window title with the name of the current terminal session, which reveals a list of sessions (with each list item containing a close button) when tapped. Signed-off-by: Jack Palevich --- src/jackpal/androidterm/Term.java | 84 +++++++++++++++++++- src/jackpal/androidterm/TermViewFlipper.java | 23 ++++++ 2 files changed, 106 insertions(+), 1 deletion(-) diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index dc84d35d1..0193d7973 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -46,10 +46,12 @@ import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; +import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; import android.widget.FrameLayout; +import android.widget.TextView; import android.widget.Toast; import jackpal.androidterm.compat.ActionBarCompat; @@ -108,6 +110,7 @@ public void onServiceConnected(ComponentName className, IBinder service) { TermService.TSBinder binder = (TermService.TSBinder) service; mTermService = binder.getService(); populateViewFlipper(); + populateWindowList(); } public void onServiceDisconnected(ComponentName arg0) { @@ -117,6 +120,48 @@ public void onServiceDisconnected(ComponentName arg0) { private ActionBarCompat mActionBar; + private WindowListAdapter mWinListAdapter; + private class WindowListActionBarAdapter extends WindowListAdapter implements UpdateCallback { + // From android.R.style in API 13 + private static final int TextAppearance_Holo_Widget_ActionBar_Title = 0x01030112; + + public WindowListActionBarAdapter(SessionList sessions) { + super(sessions); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + TextView label = new TextView(Term.this); + label.setText(getString(R.string.window_title, position + 1)); + if (AndroidCompat.SDK >= 13) { + label.setTextAppearance(Term.this, TextAppearance_Holo_Widget_ActionBar_Title); + } else { + label.setTextAppearance(Term.this, android.R.style.TextAppearance_Medium); + } + return label; + } + + @Override + public View getDropDownView(int position, View convertView, ViewGroup parent) { + return super.getView(position, convertView, parent); + } + + public void onUpdate() { + notifyDataSetChanged(); + mActionBar.setSelectedNavigationItem(mViewFlipper.getDisplayedChild()); + } + } + + private ActionBarCompat.OnNavigationListener mWinListItemSelected = new ActionBarCompat.OnNavigationListener() { + public boolean onNavigationItemSelected(int position, long id) { + int oldPosition = mViewFlipper.getDisplayedChild(); + if (position != oldPosition) { + mViewFlipper.setDisplayedChild(position); + } + return true; + } + }; + private boolean mHaveFullHwKeyboard = false; private class EmulatorViewGestureListener extends SimpleOnGestureListener { @@ -191,7 +236,12 @@ public void onCreate(Bundle icicle) { } mWifiLock = wm.createWifiLock(wifiLockMode, TermDebug.LOG_TAG); - mActionBar = ActivityCompat.getActionBar(this); + ActionBarCompat actionBar = ActivityCompat.getActionBar(this); + if (actionBar != null) { + mActionBar = actionBar; + actionBar.setNavigationMode(ActionBarCompat.NAVIGATION_MODE_LIST); + actionBar.setDisplayOptions(0, ActionBarCompat.DISPLAY_SHOW_TITLE); + } mHaveFullHwKeyboard = checkHaveFullHwKeyboard(getResources().getConfiguration()); @@ -218,6 +268,25 @@ private void populateViewFlipper() { } } + private void populateWindowList() { + if (mActionBar == null) { + // Not needed + return; + } + + if (mTermSessions != null) { + if (mWinListAdapter == null) { + WindowListAdapter adapter = new WindowListActionBarAdapter(mTermSessions); + mWinListAdapter = adapter; + mTermSessions.addCallback(adapter); + mViewFlipper.addCallback(adapter); + mActionBar.setListNavigationCallbacks(mWinListAdapter, mWinListItemSelected); + } else { + mWinListAdapter.setSessions(mTermSessions); + } + } + } + @Override public void onDestroy() { super.onDestroy(); @@ -321,6 +390,10 @@ public void onResume() { if (mTermSessions != null) { mTermSessions.addCallback(this); + if (mWinListAdapter != null) { + mTermSessions.addCallback(mWinListAdapter); + mViewFlipper.addCallback(mWinListAdapter); + } } if (mTermSessions != null && mTermSessions.size() < mViewFlipper.getChildCount()) { for (int i = 0; i < mViewFlipper.getChildCount(); ++i) { @@ -352,6 +425,10 @@ public void onPause() { mViewFlipper.pauseCurrentView(); if (mTermSessions != null) { mTermSessions.removeCallback(this); + if (mWinListAdapter != null) { + mTermSessions.removeCallback(mWinListAdapter); + mViewFlipper.removeCallback(mWinListAdapter); + } } if (AndroidCompat.SDK < 5) { @@ -389,6 +466,11 @@ public void onConfigurationChanged(Configuration newConfig) { if (v != null) { v.updateSize(true); } + + if (mWinListAdapter != null) { + // Force Android to redraw the label in the navigation dropdown + mWinListAdapter.notifyDataSetChanged(); + } } @Override diff --git a/src/jackpal/androidterm/TermViewFlipper.java b/src/jackpal/androidterm/TermViewFlipper.java index c21f31e7d..28fb5188d 100644 --- a/src/jackpal/androidterm/TermViewFlipper.java +++ b/src/jackpal/androidterm/TermViewFlipper.java @@ -17,6 +17,7 @@ package jackpal.androidterm; import java.util.Iterator; +import java.util.LinkedList; import android.content.Context; import android.util.AttributeSet; @@ -24,9 +25,12 @@ import android.widget.Toast; import android.widget.ViewFlipper; +import jackpal.androidterm.model.UpdateCallback; + public class TermViewFlipper extends ViewFlipper implements Iterable { private Context context; private Toast mToast; + private LinkedList callbacks; class ViewFlipperIterator implements Iterator { int pos = 0; @@ -47,17 +51,33 @@ public void remove() { public TermViewFlipper(Context context) { super(context); this.context = context; + callbacks = new LinkedList(); } public TermViewFlipper(Context context, AttributeSet attrs) { super(context, attrs); this.context = context; + callbacks = new LinkedList(); } public Iterator iterator() { return new ViewFlipperIterator(); } + public void addCallback(UpdateCallback callback) { + callbacks.add(callback); + } + + public void removeCallback(UpdateCallback callback) { + callbacks.remove(callback); + } + + private void notifyChange() { + for (UpdateCallback callback : callbacks) { + callback.onUpdate(); + } + } + public void pauseCurrentView() { EmulatorView view = (EmulatorView) getCurrentView(); if (view == null) { @@ -93,6 +113,7 @@ public void showPrevious() { super.showPrevious(); showTitle(); resumeCurrentView(); + notifyChange(); } @Override @@ -101,6 +122,7 @@ public void showNext() { super.showNext(); showTitle(); resumeCurrentView(); + notifyChange(); } @Override @@ -109,5 +131,6 @@ public void setDisplayedChild(int position) { super.setDisplayedChild(position); showTitle(); resumeCurrentView(); + notifyChange(); } } From cc537b83853f2e124240e308eef6889f9b02c98c Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Sat, 14 Jan 2012 04:24:18 -0800 Subject: [PATCH 206/847] Add support for autohiding action bar Offer a preference on Honeycomb and later to allow the action bar to be hidden. When hidden, the action bar can be displayed by tapping on the upper half of the EmulatorView or by pressing the Menu key (if available). Tapping on the bottom half of the EmulatorView brings up the soft keyboard (unless a hardware keyboard is attached, in which case it also brings up the action bar). Signed-off-by: Jack Palevich --- res/values/arrays.xml | 13 +++ res/values/defaults.xml | 1 + res/values/strings.xml | 4 + res/values/styles.xml | 7 ++ res/xml/preferences.xml | 9 ++ src/jackpal/androidterm/Term.java | 90 +++++++++++++++++-- src/jackpal/androidterm/TermPreferences.java | 8 ++ .../androidterm/util/TermSettings.java | 13 +++ 8 files changed, 139 insertions(+), 6 deletions(-) diff --git a/res/values/arrays.xml b/res/values/arrays.xml index 532237758..a9363f7b5 100644 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -27,6 +27,19 @@ 0 + + + Always show action bar + Hide action bar (touch top of screen or Menu key to show) + + + + + + 1 + 2 + + Non-blinking cursor Blinking cursor diff --git a/res/values/defaults.xml b/res/values/defaults.xml index 72619ba68..38383c1dd 100644 --- a/res/values/defaults.xml +++ b/res/values/defaults.xml @@ -2,6 +2,7 @@ 0 + 1 0 0 10 diff --git a/res/values/strings.xml b/res/values/strings.xml index a8b023e5e..26d2f8f6b 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -50,6 +50,10 @@ Show/hide status bar. Status bar + Action bar + Choose the action bar\'s behavior (Android 3.0 and up). + Action bar behavior + Cursor style Choose cursor style. Cursor style diff --git a/res/values/styles.xml b/res/values/styles.xml index 5ad5914ea..fb5202ae8 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -24,5 +24,12 @@ @null @null + + diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml index 55991c34e..d338c7fea 100644 --- a/res/xml/preferences.xml +++ b/res/xml/preferences.xml @@ -29,6 +29,15 @@ android:entryValues="@array/entryvalues_statusbar_preference" android:dialogTitle="@string/dialog_title_statusbar_preference" /> + + 0PJAZkouz3F~@LFKP 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 PIRr2@vP9Jz;Nt!U*I|wz literal 0 HcmV?d00001 From b8fd6a905c751fa9487d5c14e39419917f92c331 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Mon, 16 Jan 2012 12:50:54 -0800 Subject: [PATCH 212/847] Remove a few compiler warnings. Should not affect running program at all. --- src/jackpal/androidterm/EmulatorView.java | 3 +-- src/jackpal/androidterm/Term.java | 1 - src/jackpal/androidterm/TermService.java | 4 +--- src/jackpal/androidterm/WindowList.java | 6 ------ src/jackpal/androidterm/compat/ServiceForegroundCompat.java | 3 ++- src/jackpal/androidterm/util/SessionList.java | 1 + 6 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/jackpal/androidterm/EmulatorView.java b/src/jackpal/androidterm/EmulatorView.java index 9200b87c8..0923e34c4 100644 --- a/src/jackpal/androidterm/EmulatorView.java +++ b/src/jackpal/androidterm/EmulatorView.java @@ -32,14 +32,12 @@ import android.os.Bundle; import android.os.Handler; import android.text.ClipboardManager; -import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; import android.view.GestureDetector; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; -import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.CorrectionInfo; import android.view.inputmethod.EditorInfo; @@ -806,6 +804,7 @@ public boolean onDown(MotionEvent e) { } } + @SuppressWarnings("deprecation") private boolean onTouchEventWhileSelectingText(MotionEvent ev) { int action = ev.getAction(); int cx = (int)(ev.getX() / mCharacterWidth); diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index a39c0bf1a..50545d930 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -52,7 +52,6 @@ import android.view.inputmethod.InputMethodManager; import android.widget.FrameLayout; import android.widget.TextView; -import android.widget.Toast; import jackpal.androidterm.compat.ActionBarCompat; import jackpal.androidterm.compat.ActivityCompat; diff --git a/src/jackpal/androidterm/TermService.java b/src/jackpal/androidterm/TermService.java index 71992cd94..c3e8a7808 100644 --- a/src/jackpal/androidterm/TermService.java +++ b/src/jackpal/androidterm/TermService.java @@ -22,11 +22,9 @@ import android.content.Intent; import android.util.Log; import android.app.Notification; -import android.app.NotificationManager; import android.app.PendingIntent; import jackpal.androidterm.compat.ServiceForegroundCompat; -import jackpal.androidterm.model.UpdateCallback; import jackpal.androidterm.session.TermSession; import jackpal.androidterm.util.SessionList; @@ -76,7 +74,7 @@ public void onCreate() { PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notifyIntent, 0); notification.setLatestEventInfo(this, getText(R.string.application_terminal), getText(R.string.service_notify_text), pendingIntent); compat.startForeground(RUNNING_NOTIFICATION, notification); - + Log.d(TermDebug.LOG_TAG, "TermService started"); return; } diff --git a/src/jackpal/androidterm/WindowList.java b/src/jackpal/androidterm/WindowList.java index a45c44cc7..2b96249aa 100644 --- a/src/jackpal/androidterm/WindowList.java +++ b/src/jackpal/androidterm/WindowList.java @@ -25,20 +25,14 @@ import android.os.IBinder; import android.util.AttributeSet; import android.util.Log; -import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.ListView; -import android.widget.TextView; import jackpal.androidterm.compat.ActionBarCompat; import jackpal.androidterm.compat.ActivityCompat; import jackpal.androidterm.compat.AndroidCompat; -import jackpal.androidterm.model.UpdateCallback; -import jackpal.androidterm.session.TermSession; import jackpal.androidterm.util.SessionList; public class WindowList extends ListActivity { diff --git a/src/jackpal/androidterm/compat/ServiceForegroundCompat.java b/src/jackpal/androidterm/compat/ServiceForegroundCompat.java index 3c7771bdf..25f34dc94 100644 --- a/src/jackpal/androidterm/compat/ServiceForegroundCompat.java +++ b/src/jackpal/androidterm/compat/ServiceForegroundCompat.java @@ -23,6 +23,7 @@ import android.util.Log; import android.app.Notification; import android.app.NotificationManager; +import android.content.Context; /* Provide startForeground() and stopForeground() compatibility, using the current interfaces where available and the deprecated setForeground() @@ -82,7 +83,7 @@ public void stopForeground(boolean removeNotify) { public ServiceForegroundCompat(Service service) { this.service = service; - mNM = (NotificationManager)service.getSystemService(service.NOTIFICATION_SERVICE); + mNM = (NotificationManager)service.getSystemService(Context.NOTIFICATION_SERVICE); Class clazz = service.getClass(); diff --git a/src/jackpal/androidterm/util/SessionList.java b/src/jackpal/androidterm/util/SessionList.java index a68208db8..e2e7f0be1 100644 --- a/src/jackpal/androidterm/util/SessionList.java +++ b/src/jackpal/androidterm/util/SessionList.java @@ -27,6 +27,7 @@ * An ArrayList of TermSessions which allows users to register callbacks in * order to be notified when the list is changed. */ +@SuppressWarnings("serial") public class SessionList extends ArrayList { LinkedList callbacks = new LinkedList(); From 0d3cec6b7e3f3bf784338b19f0ed43036635f012 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Mon, 16 Jan 2012 13:06:01 -0800 Subject: [PATCH 213/847] Make toasts legible. Center them, rather than the default, which is to show them near the bottom of the screen. The old position would show the toasts on top of the soft keyboard, where they were hard to see. Also make the reset message stay up longer since it's more text to read. --- src/jackpal/androidterm/Term.java | 5 ++++- src/jackpal/androidterm/TermViewFlipper.java | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index a002cefbd..b3208a993 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -52,6 +52,7 @@ import android.view.inputmethod.InputMethodManager; import android.widget.FrameLayout; import android.widget.TextView; +import android.widget.Toast; import jackpal.androidterm.compat.ActionBarCompat; import jackpal.androidterm.compat.ActivityCompat; @@ -520,7 +521,9 @@ public boolean onOptionsItemSelected(MenuItem item) { startActivityForResult(new Intent(this, WindowList.class), REQUEST_CHOOSE_WINDOW); } else if (id == R.id.menu_reset) { doResetTerminal(); - Toast.makeText(this,R.string.reset_toast_notification,Toast.LENGTH_SHORT).show(); + Toast toast = Toast.makeText(this,R.string.reset_toast_notification,Toast.LENGTH_LONG); + toast.setGravity(Gravity.CENTER, 0, 0); + toast.show(); } else if (id == R.id.menu_send_email) { doEmailTranscript(); } else if (id == R.id.menu_special_keys) { diff --git a/src/jackpal/androidterm/TermViewFlipper.java b/src/jackpal/androidterm/TermViewFlipper.java index 28fb5188d..a296213ad 100644 --- a/src/jackpal/androidterm/TermViewFlipper.java +++ b/src/jackpal/androidterm/TermViewFlipper.java @@ -21,6 +21,7 @@ import android.content.Context; import android.util.AttributeSet; +import android.view.Gravity; import android.view.View; import android.widget.Toast; import android.widget.ViewFlipper; @@ -101,6 +102,7 @@ private void showTitle() { String title = context.getString(R.string.window_title, getDisplayedChild()+1); if (mToast == null) { mToast = Toast.makeText(context, title, Toast.LENGTH_SHORT); + mToast.setGravity(Gravity.CENTER, 0, 0); } else { mToast.setText(title); } From b5147ed81b83587e1f3abf93dc664b7ba4afc7f0 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Mon, 16 Jan 2012 13:35:51 -0800 Subject: [PATCH 214/847] Add confirmation dialog for closing window from action bar. It's safer than just deleting the session. --- res/values/strings.xml | 1 + src/jackpal/androidterm/Term.java | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 13971527e..367ecc07f 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -123,4 +123,5 @@ FNKEY 1..9 : F1-F9\nFNKEY 0 : F10\nFNKEY W : Up\nFNKEY A : Left\nFNKEY S : Down\nFNKEY D : Right\nFNKEY P : PageUp\nFNKEY N : PageDown\nFNKEY T : Tab\nFNKEY L : | (pipe)\nFNKEY U : _ (underscore)\nFNKEY E : Control-[ (ESC)\nFNKEY X : Delete\nFNKEY I : Insert\nFNKEY H : Home\nFNKEY F : End\nFNKEY . : Control-\\\n No function key set. + Close this window? diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index e812544a8..143b4ce64 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -22,6 +22,7 @@ import android.app.AlertDialog; import android.content.ComponentName; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.ServiceConnection; @@ -518,7 +519,7 @@ public boolean onOptionsItemSelected(MenuItem item) { } else if (id == R.id.menu_new_window) { doCreateNewWindow(); } else if (id == R.id.menu_close_window) { - doCloseWindow(); + confirmCloseWindow(); } else if (id == R.id.menu_window_list) { startActivityForResult(new Intent(this, WindowList.class), REQUEST_CHOOSE_WINDOW); } else if (id == R.id.menu_reset) { @@ -560,6 +561,19 @@ private void doCreateNewWindow() { mViewFlipper.setDisplayedChild(mViewFlipper.getChildCount()-1); } + private void confirmCloseWindow() { + final AlertDialog.Builder b = new AlertDialog.Builder(this); + b.setIcon(android.R.drawable.ic_dialog_alert); + b.setMessage(R.string.confirm_window_close_message); + b.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + doCloseWindow(); + } + }); + b.setNegativeButton(android.R.string.no, null); + b.show(); + } + private void doCloseWindow() { if (mTermSessions == null) { return; From 0685f1894bc19e592ba2a07e9e92e22723ccc150 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Mon, 16 Jan 2012 13:40:01 -0800 Subject: [PATCH 215/847] Version 1.0.40 version code 41 --- AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index b8db85bab..6569a3e03 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,6 +1,6 @@ From c4e8ccc6eb37c6829bae56f214086794460bb1b1 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Mon, 16 Jan 2012 14:25:39 -0800 Subject: [PATCH 216/847] Remove non-working portugese translation. --- res/values-pt/arrays.xml | 115 ------------------------------------ res/values-pt/strings.xml | 120 -------------------------------------- 2 files changed, 235 deletions(-) delete mode 100644 res/values-pt/arrays.xml delete mode 100644 res/values-pt/strings.xml diff --git a/res/values-pt/arrays.xml b/res/values-pt/arrays.xml deleted file mode 100644 index 5b85ff6b9..000000000 --- a/res/values-pt/arrays.xml +++ /dev/null @@ -1,115 +0,0 @@ - - - - - - Show status bar - Hide status bar - - - - Non-blinking cursor - Blinking cursor - - - - Rectangle - Underline - Vertical bar - - - - 4 x 8 pixels - 6 pt - 7 pt - 8 pt - 9 pt - 10 pt - 12 pt - 14 pt - 16 pt - 20 pt - - - - Black text on white - White text on black - White text on blue - Green text on black - Amber text on black - Red text on black - - - - Closes all terminal windows - Closes this terminal window only - Closes activity, leaving sessions running - Sends ESC to terminal - Sends TAB to terminal - - - - Jog ball - \@ key - Left Alt key - Right Alt key - Vol Up key - Vol Down key - Camera key - None - - - - Jog ball - \@ key - Left Alt key - Right Alt key - Vol Up key - Vol Down key - Camera key - None - - - - Character-based - Word-based - - - - - Ball - \@ - Left-Alt - Right-Alt - Vol-Up - Vol-Dn - Camera - None - - - - - Ball - \@ - Left-Alt - Right-Alt - Vol-Up - Vol-Dn - Camera - None - - \ No newline at end of file diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml deleted file mode 100644 index 048965592..000000000 --- a/res/values-pt/strings.xml +++ /dev/null @@ -1,120 +0,0 @@ - - - - Terminal Emulator - Preferences - New window - Close window - Windows - Prev window - Next window - Reset term - Email to - Special keys - Toggle soft keyboard - - Estado d\'esta janela foi zerado. - - Take WakeLock - Drop WakeLock - Take WifiLock - Drop WifiLock - - Edit text - Select text - Copy all - Paste - - Window %1$d - - Terminal session is running - - Terminal session finished - - - Screen - - Status bar - Show/hide status bar. - Status bar - - Cursor style - Choose cursor style. - Cursor style - - Cursor blink - Choose cursor blink. - Cursor blink - - Text - - Default to UTF-8 - Whether UTF-8 mode is enabled by default. - - Font size - Choose character height in points. - Font size - - Colors - Choose text color. - Text color - - Keyboard - - Back button behavior - Choose what pressing the back button does. - Back button behavior - - Control key - Choose control key. - Control key - - Fn key - Choose Fn key. - Fn key - - Input method - Choose input method for soft keyboard. - Input method - - Shell - Command line - Specify the shell command line. - Shell - - Initial command - Sent to the shell when it starts. - Initial Command - - Terminal type - What terminal type to report to the shell. - Terminal type - - Close window on exit - Whether a window should close when its shell exits. - - Control and Function Keys - - CTRLKEY Space : Control-@ (NUL)\nCTRLKEY A..Z : Control-A..Z\nCTRLKEY 5 : Control-]\nCTRLKEY 6 : Control-^\nCTRLKEY 7 : Control-_\nCTRLKEY 9 : F11\nCTRLKEY 0 : F12 - No control key set. - - FNKEY 1..9 : F1-F9\nFNKEY 0 : F10\nFNKEY W : Up\nFNKEY A : Left\nFNKEY S : Down\nFNKEY D : Right\nFNKEY P : PageUp\nFNKEY N : PageDown\nFNKEY T : Tab\nFNKEY L : | (pipe)\nFNKEY U : _ (underscore)\nFNKEY E : Control-[ (ESC)\nFNKEY X : Delete\nFNKEY I : Insert\nFNKEY H : Home\nFNKEY F : End\nFNKEY . : Control-\\\n - No function key set. - - \ No newline at end of file From 3b1a6884b9a45beffbb1177e07cb73de59e17619 Mon Sep 17 00:00:00 2001 From: eauland Date: Tue, 17 Jan 2012 01:08:02 +0100 Subject: [PATCH 217/847] Update French translation for your new options. Regards. --- res/values-fr/strings.xml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml index 4f5050b6f..d3024d5bb 100644 --- a/res/values-fr/strings.xml +++ b/res/values-fr/strings.xml @@ -28,7 +28,7 @@ Touches spéciales Afficher/Masquer Clavier - L\'état de cette fenêtre vient d\'être mis a défaut. + Etat de cette fenêtre par défaut. Activer WakeLock Désactiver WakeLock @@ -53,6 +53,10 @@ Afficher/Masquer la barre de statut Barre de statut + Barre d\'actions + Choisir le comportement de la barre d\'actions (A partir d\'Android 3.0) + Barre d\'actions + Style du curseur Choisir le style du curseur Style du curseur @@ -109,4 +113,6 @@ Quitter le terminal en exécutant la commande exit Touches Control et Fonction + + Fermer cette fenêtre ? From aee32fa2c2b7c05a1a03ea1a3b9d49118b00bb48 Mon Sep 17 00:00:00 2001 From: eauland Date: Tue, 17 Jan 2012 01:17:20 +0100 Subject: [PATCH 218/847] Update French Translation (arrays.xml) for action bar. Regards. --- res/values-fr/arrays.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/res/values-fr/arrays.xml b/res/values-fr/arrays.xml index 2043fcb76..90595699d 100644 --- a/res/values-fr/arrays.xml +++ b/res/values-fr/arrays.xml @@ -21,6 +21,12 @@ Masquer la barre de statut + + + Toujours afficher la barre d\'actions + Cacher la barre d\'actions (Touche Haut de l\'écran ou la touche Menu de l\'appareil pour afficher) + + Curseur non clignotant Curseur clignotant From 9d6c156a7d18e05f5a6c77a6aaa7228bef0d2c43 Mon Sep 17 00:00:00 2001 From: damor Date: Tue, 17 Jan 2012 13:32:24 +0100 Subject: [PATCH 219/847] Added portuguese translation and a small german update --- res/values-de/strings.xml | 9 ++- res/values-pt/arrays.xml | 115 +++++++++++++++++++++++++++++++++++ res/values-pt/strings.xml | 125 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 247 insertions(+), 2 deletions(-) create mode 100644 res/values-pt/arrays.xml create mode 100644 res/values-pt/strings.xml diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml index 41627a8d6..53989c4e5 100644 --- a/res/values-de/strings.xml +++ b/res/values-de/strings.xml @@ -51,9 +51,13 @@ Die Statuszeile anzeigen/verbergen. Statuszeile - Aussehen Cursor + Aktionsleiste + Wählen sie das Verhalten der Aktionsleiste (ab Android 3.0). + Verhalten der Aktionsleiste + + Aussehen des Cursor Das Aussehen des Cursors auswählen. - Aussehen Cursor + Aussehen des Cursor Cursor-Blinken Die Art auswählen, wie der Cursor blinken soll. @@ -116,4 +120,5 @@ FNKEY 1..9 : F1-F9\nFNKEY 0 : F10\nFNKEY W : Up\nFNKEY A : Left\nFNKEY S : Down\nFNKEY D : Right\nFNKEY P : PageUp\nFNKEY N : PageDown\nFNKEY T : Tab\nFNKEY L : | (pipe)\nFNKEY U : _ (underscore)\nFNKEY E : Control-[ (ESC)\nFNKEY X : Delete\nFNKEY I : Insert\nFNKEY H : Home\nFNKEY F : End\nFNKEY . : Control-\\\n Keine Funktionstaste fergelegt. + Dieses Fenster schliessen? \ No newline at end of file diff --git a/res/values-pt/arrays.xml b/res/values-pt/arrays.xml new file mode 100644 index 000000000..493d8eaf7 --- /dev/null +++ b/res/values-pt/arrays.xml @@ -0,0 +1,115 @@ + + + + + + Mostrar barra de estado + Occultar barra de estado + + + + Cursor constante + Cursor piscando + + + + Retângulo + Sublinhado + Barra vertical + + + + 4 x 8 pixels + 6 pt + 7 pt + 8 pt + 9 pt + 10 pt + 12 pt + 14 pt + 16 pt + 20 pt + + + + Texto preto sobre fundo branco + Texto branco sobre fundo preto + Texto branco sobre fundo azul + Texto verde sobre fundo preto + Texto âmbar sobre fundo preto + Texto vermelho sobre fundo preto + + + + Fecha todas as janelas do terminal + Fecha esta janela de terminal só + Fecha atividade, deixando sessões em execução + Envia ESC para terminal + Envia TAB para terminal + + + + Trackball + Chave \@ + Tecla Alt esquerda + Tecla Alt direita + Tecla Volume mais + Tecla Volume menos + Tecla Camera + Nenhum + + + + Trackball + Chave \@ + Tecla Alt esquerda + Tecla Alt direita + Tecla Volume mais + Tecla Volume menos + Tecla Camera + Nenhum + + + + Baseado sobre caractere + Baseado sobre palavra + + + + + Ball + \@ + Alt Esquerdo + Alt Direito + Vol-Mais + Vol-Menos + Camera + Nenhum + + + + + Ball + \@ + Alt Esquerdo + Alt Direito + Vol-Mais + Vol-Menos + Camera + Nenhum + + \ No newline at end of file diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml new file mode 100644 index 000000000..377cb21a8 --- /dev/null +++ b/res/values-pt/strings.xml @@ -0,0 +1,125 @@ + + + + Terminal Emulator + Preferencias + Nova janela + Fechar janela + Janelas + Janela anterior + Próxima janela + Zerar term + Email para + Teclas especias + Alternar teclado virtual + + Estado desta janela foi zerado. + + Take WakeLock + Drop WakeLock + Take WifiLock + Drop WifiLock + + Editar texto + Selecionar texto + Copiar tudo + Colar + + Janela %1$d + + Sessão terminal esta em execução + + Sessão terminal terminou + + + Ecrã + + Barra de estado + Mostar/Ocultar barra de estado. + Barra de estado + + Barra de ação + Escolha o comportamento da barra de ação (a partir de Android 3.0). + Comportamento da barra de Ação + + Estilo de cursor + Escolha o estilo de cursor. + Estilo de cursor + + Intermitência do cursor + Escolha a intermitência do cursor. + Intermitência do cursor + + Texto + + Mudar padrão para UTF-8 + Se UTF-8 é ativado por padrão. + + Tamanho dos caracteres + Escolha altura dos caracteres em pontos. + Tamanho dos caracteres + + Cores + Escolha a cor do texto. + Cor do texto + + Teclado + + Botão voltar + Escolha o que pressionar o botão de voltar faz. + Comportamento do botão voltar + + Tecla Control + Escolha tecla de controle. + Tecla Control + + Tecla Fn + Escolha a tecla Fn. + Tecla Fn + + Método de entrada + Escolha método de entrada para teclado virtual. + Método de entrada + + Shell + Linha de comando + Especifique a linha de comando shell. + Shell + + Comando inicial + Enviado para o shell quando é iniciado. + Comando inicial + + Tipo de terminal + Que tipo de terminal para informar o shell. + Tipo de terminal + + Fechar janela ao sair + Se uma janela deve se fechar quando sai do shell. + + Chaves de controle e função + + CTRLKEY Space : Control-@ (NUL)\nCTRLKEY A..Z : Control-A..Z\nCTRLKEY 5 : Control-]\nCTRLKEY 6 : Control-^\nCTRLKEY 7 : Control-_\nCTRLKEY 9 : F11\nCTRLKEY 0 : F12 + Nenhuma tecla de controlo definida. + + FNKEY 1..9 : F1-F9\nFNKEY 0 : F10\nFNKEY W : Up\nFNKEY A : Left\nFNKEY S : Down\nFNKEY D : Right\nFNKEY P : PageUp\nFNKEY N : PageDown\nFNKEY T : Tab\nFNKEY L : | (pipe)\nFNKEY U : _ (underscore)\nFNKEY E : Control-[ (ESC)\nFNKEY X : Delete\nFNKEY I : Insert\nFNKEY H : Home\nFNKEY F : End\nFNKEY . : Control-\\\n + Nenhuma tecla de função definida. + + Fechar esta janela? + \ No newline at end of file From a7df732a4a7d4214d4beb484bcdb20f50e2bddc7 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Mon, 23 Jan 2012 01:39:56 -0800 Subject: [PATCH 220/847] Make sure IME closes when last window is closed via the confirmation dialog The request to close the IME doesn't work reliably when the confirmation dialog is displayed on top of the activity window, so dismiss the dialog and post the request to close the terminal window via a Handler instead. Signed-off-by: Jack Palevich --- src/jackpal/androidterm/Term.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index 143b4ce64..4613b3569 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -32,6 +32,7 @@ import android.net.Uri; import android.net.wifi.WifiManager; import android.os.Bundle; +import android.os.Handler; import android.os.IBinder; import android.os.PowerManager; import android.preference.PreferenceManager; @@ -223,6 +224,8 @@ public boolean onKey(View v, int keyCode, KeyEvent event) { } }; + private Handler mHandler = new Handler(); + @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); @@ -565,9 +568,15 @@ private void confirmCloseWindow() { final AlertDialog.Builder b = new AlertDialog.Builder(this); b.setIcon(android.R.drawable.ic_dialog_alert); b.setMessage(R.string.confirm_window_close_message); + final Runnable closeWindow = new Runnable() { + public void run() { + doCloseWindow(); + } + }; b.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { - doCloseWindow(); + dialog.dismiss(); + mHandler.post(closeWindow); } }); b.setNegativeButton(android.R.string.no, null); From 9ca6313151f1ea2a2cd417386341ec245ff663c1 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Mon, 23 Jan 2012 01:40:02 -0800 Subject: [PATCH 221/847] Update LayoutParams and redo layout on EmulatorView resize At the moment, when we resize an EmulatorView, we're only resizing the contents and the area on which we're drawing, not the view itself. This is fine in most cases (any blank space is being covered by the soft keyboard), but on ICS, when the status bar is disabled and the action bar is always showing, rotating from portrait to landscape with the soft keyboard showing appears to result in an EmulatorView large enough to push the action bar off screen. Work around this by resizing the EmulatorView itself through adjustment of its LayoutParams, not just the contents. Signed-off-by: Jack Palevich --- src/jackpal/androidterm/EmulatorView.java | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/jackpal/androidterm/EmulatorView.java b/src/jackpal/androidterm/EmulatorView.java index fe9410a46..8fb4f16b7 100644 --- a/src/jackpal/androidterm/EmulatorView.java +++ b/src/jackpal/androidterm/EmulatorView.java @@ -38,6 +38,7 @@ import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; +import android.view.ViewGroup.LayoutParams; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.CorrectionInfo; import android.view.inputmethod.EditorInfo; @@ -212,6 +213,8 @@ public void run() { } }; + private boolean mRedoLayout = false; + private GestureDetector mGestureDetector; private GestureDetector.OnGestureListener mExtGestureListener; private float mScrollRemainder; @@ -985,11 +988,10 @@ private void updateSize(int w, int h) { public void updateSize(boolean force) { if (mKnownSize) { getWindowVisibleDisplayFrame(mVisibleRect); - // Work around bug in getWindowVisibleDisplayFrame - if (AndroidCompat.SDK < 10) { - if (!mSettings.showStatusBar()) { - mVisibleRect.top = 0; - } + /* Work around bug in getWindowVisibleDisplayFrame, and avoid + distracting visual glitch otherwise */ + if (!mSettings.showStatusBar()) { + mVisibleRect.top = 0; } if (mSizeCallback != null) { // Let activity adjust our size @@ -1001,6 +1003,13 @@ public void updateSize(boolean force) { if (force || w != mVisibleWidth || h != mVisibleHeight) { mVisibleWidth = w; mVisibleHeight = h; + + LayoutParams params = getLayoutParams(); + params.width = w; + params.height = h; + setLayoutParams(params); + mRedoLayout = true; + updateSize(mVisibleWidth, mVisibleHeight); } } @@ -1021,6 +1030,10 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { @Override protected void onDraw(Canvas canvas) { updateSize(false); + if (mRedoLayout) { + requestLayout(); + mRedoLayout = false; + } int w = getWidth(); int h = getHeight(); From 7515d24cb6684fa2702efff4a68b6a25030f30e7 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Mon, 23 Jan 2012 01:40:10 -0800 Subject: [PATCH 222/847] Change TermViewFlipper background color to match EmulatorView background With the previous patch, EmulatorViews no longer always take up the whole of our activity, causing the TermViewFlipper's background to show through briefly when opening the soft keyboard. Make it match the EmulatorViews' background color to avoid a distracting visual flash. Signed-off-by: Jack Palevich --- src/jackpal/androidterm/Term.java | 1 + src/jackpal/androidterm/TermViewFlipper.java | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index 4613b3569..20ada3f56 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -388,6 +388,7 @@ private void updatePrefs() { DisplayMetrics metrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(metrics); + mViewFlipper.updatePrefs(mSettings); for (View v : mViewFlipper) { ((EmulatorView) v).setDensity(metrics); ((EmulatorView) v).updatePrefs(mSettings); diff --git a/src/jackpal/androidterm/TermViewFlipper.java b/src/jackpal/androidterm/TermViewFlipper.java index a296213ad..f9f3bbf4e 100644 --- a/src/jackpal/androidterm/TermViewFlipper.java +++ b/src/jackpal/androidterm/TermViewFlipper.java @@ -27,6 +27,7 @@ import android.widget.ViewFlipper; import jackpal.androidterm.model.UpdateCallback; +import jackpal.androidterm.util.TermSettings; public class TermViewFlipper extends ViewFlipper implements Iterable { private Context context; @@ -61,6 +62,11 @@ public TermViewFlipper(Context context, AttributeSet attrs) { callbacks = new LinkedList(); } + public void updatePrefs(TermSettings settings) { + int[] colorScheme = settings.getColorScheme(); + setBackgroundColor(colorScheme[3]); + } + public Iterator iterator() { return new ViewFlipperIterator(); } From dfa93dd41b7ad43ee843cb69cd6f89308c67c932 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Mon, 23 Jan 2012 01:40:17 -0800 Subject: [PATCH 223/847] Delay allocation of the transcript until after we know the terminal's size During the modularization for by the multisession patches, the lazy initialization which used to ensure that the transcript wasn't allocated until its final size was known was lost. This results in the transcript always being allocated at least twice for a newly created session (unless the final width of the window is exactly 80 columns). To prevent this, delay allocation of the transcript in TermSession until after the size is set with a call to updateSize(). This requires some changes in the EmulatorView's initialization code to ensure that the TranscriptScreen and TerminalEmulator objects aren't used before they're allocated. Signed-off-by: Jack Palevich --- src/jackpal/androidterm/EmulatorView.java | 37 +++++++++-------- .../androidterm/session/TermSession.java | 41 ++++++++++++------- 2 files changed, 48 insertions(+), 30 deletions(-) diff --git a/src/jackpal/androidterm/EmulatorView.java b/src/jackpal/androidterm/EmulatorView.java index 8fb4f16b7..ba2888389 100644 --- a/src/jackpal/androidterm/EmulatorView.java +++ b/src/jackpal/androidterm/EmulatorView.java @@ -105,7 +105,7 @@ public class EmulatorView extends View implements GestureDetector.OnGestureListe /** * Text size. Zero means 4 x 8 font. */ - private int mTextSize; + private int mTextSize = 10; private int mCursorStyle; private int mCursorBlink; @@ -113,13 +113,13 @@ public class EmulatorView extends View implements GestureDetector.OnGestureListe /** * Foreground color. */ - private int mForeground; + private int mForeground = TermSettings.WHITE; private int mForegroundIndex; /** * Background color. */ - private int mBackground; + private int mBackground = TermSettings.BLACK; private int mBackgroundIndex; /** @@ -619,10 +619,11 @@ private void commonConstructor(Context context, TermSession session) { setFocusable(true); setFocusableInTouchMode(true); - initialize(session); - session.setUpdateCallback(mUpdateNotify); + mTermSession = session; // XXX We should really be able to fetch this from within TermSession session.setProcessExitMessage(context.getString(R.string.process_exit_message)); + + mKeyListener = new TermKeyListener(session); } public void setWindowSizeCallback(WindowSizeCallback callback) { @@ -650,20 +651,16 @@ protected int computeVerticalScrollOffset() { /** * Call this to initialize the view. - * - * @param session The terminal session this view will be displaying */ - private void initialize(TermSession session) { - mTermSession = session; - mTranscriptScreen = session.getTranscriptScreen(); - mEmulator = session.getEmulator(); + private void initialize() { + TermSession session = mTermSession; - mKeyListener = new TermKeyListener(session); - mTextSize = 10; - mForeground = TermSettings.WHITE; - mBackground = TermSettings.BLACK; updateText(); + mTranscriptScreen = session.getTranscriptScreen(); + mEmulator = session.getEmulator(); + session.setUpdateCallback(mUpdateNotify); + requestFocus(); } @@ -966,8 +963,10 @@ private void updateText() { protected void onSizeChanged(int w, int h, int oldw, int oldh) { if (!mKnownSize) { mKnownSize = true; + initialize(); + } else { + updateSize(false); } - updateSize(false); } private void updateSize(int w, int h) { @@ -1034,6 +1033,12 @@ protected void onDraw(Canvas canvas) { requestLayout(); mRedoLayout = false; } + + if (mEmulator == null) { + // Not ready yet + return; + } + int w = getWidth(); int h = getHeight(); diff --git a/src/jackpal/androidterm/session/TermSession.java b/src/jackpal/androidterm/session/TermSession.java index b115fe18a..d6cb632a6 100644 --- a/src/jackpal/androidterm/session/TermSession.java +++ b/src/jackpal/androidterm/session/TermSession.java @@ -51,6 +51,8 @@ public class TermSession { private FileDescriptor mTermFd; private FileOutputStream mTermOut; private FileInputStream mTermIn; + private String mInitialCommand; + private Thread mWatcherThread; private TranscriptScreen mTranscriptScreen; private TerminalEmulator mEmulator; @@ -65,9 +67,6 @@ public class TermSession { private String mProcessExitMessage; - private static final int DEFAULT_COLUMNS = 80; - private static final int DEFAULT_ROWS = 24; - // Number of rows in the transcript private static final int TRANSCRIPT_ROWS = 10000; @@ -107,14 +106,9 @@ public TermSession(TermSettings settings, FinishCallback finishCallback, String mProcId = processId[0]; mTermOut = new FileOutputStream(mTermFd); mTermIn = new FileInputStream(mTermFd); + mInitialCommand = initialCommand; - int[] colorScheme = settings.getColorScheme(); - mTranscriptScreen = new TranscriptScreen(DEFAULT_COLUMNS, TRANSCRIPT_ROWS, DEFAULT_ROWS, colorScheme[0], colorScheme[2]); - mEmulator = new TerminalEmulator(settings, mTranscriptScreen, DEFAULT_COLUMNS, DEFAULT_ROWS, mTermOut); - - mIsRunning = true; - - Thread watcher = new Thread() { + mWatcherThread = new Thread() { @Override public void run() { Log.i(TermDebug.LOG_TAG, "waiting for: " + mProcId); @@ -123,8 +117,7 @@ public void run() { mMsgHandler.sendMessage(mMsgHandler.obtainMessage(PROCESS_EXITED, result)); } }; - watcher.setName("Process watcher"); - watcher.start(); + mWatcherThread.setName("Process watcher"); mWriteCharBuffer = CharBuffer.allocate(2); mWriteByteBuffer = ByteBuffer.allocate(4); @@ -157,9 +150,19 @@ public void run() { } }; mPollingThread.setName("Input reader"); + } + + private void initializeEmulator(int columns, int rows) { + TermSettings settings = mSettings; + int[] colorScheme = settings.getColorScheme(); + mTranscriptScreen = new TranscriptScreen(columns, TRANSCRIPT_ROWS, rows, colorScheme[0], colorScheme[2]); + mEmulator = new TerminalEmulator(settings, mTranscriptScreen, columns, rows, mTermOut); + + mIsRunning = true; + mWatcherThread.start(); mPollingThread.start(); - sendInitialCommand(initialCommand); + sendInitialCommand(mInitialCommand); } private void sendInitialCommand(String initialCommand) { @@ -277,7 +280,12 @@ public void setUpdateCallback(UpdateCallback notify) { public void updateSize(int columns, int rows) { // Inform the attached pty of our new size: Exec.setPtyWindowSize(mTermFd, rows, columns, 0, 0); - mEmulator.updateSize(columns, rows); + + if (mEmulator == null) { + initializeEmulator(columns, rows); + } else { + mEmulator.updateSize(columns, rows); + } } public String getTranscriptText() { @@ -303,6 +311,11 @@ private void readFromProcess() { public void updatePrefs(TermSettings settings) { mSettings = settings; + if (mEmulator == null) { + // Not initialized yet, we'll pick up the settings then + return; + } + mEmulator.updatePrefs(settings); int[] colorScheme = settings.getColorScheme(); From d953f98b6b289c95d29484d346ac664b16a3ce3a Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Wed, 25 Jan 2012 17:00:50 -0800 Subject: [PATCH 224/847] Handle Android 3.0+ full keyboards correctly. Our toggling behavior was confusing people, especially with respect to the CTRL key on Android 3.0+ full physical keyboards. We now never toggle when we receive the physical ctrl key. The physical control key is only present on Android 3.0+ full keyboards. We now leave it to the system to handle physical caps lock. Caps lock is only present on Android 3.0+ full keyboards. We don't toggle shift or alt on keyboards that report that they are MODIFIER_BEHAVIOR_CHORDED. (i.e. Android 3.0+ full keyboards.) --- src/jackpal/androidterm/EmulatorView.java | 84 ++++++++++++++----- .../compat/KeyCharacterMapCompat.java | 46 ++++++++++ 2 files changed, 109 insertions(+), 21 deletions(-) create mode 100644 src/jackpal/androidterm/compat/KeyCharacterMapCompat.java diff --git a/src/jackpal/androidterm/EmulatorView.java b/src/jackpal/androidterm/EmulatorView.java index ba2888389..26e7c3eea 100644 --- a/src/jackpal/androidterm/EmulatorView.java +++ b/src/jackpal/androidterm/EmulatorView.java @@ -35,6 +35,7 @@ import android.util.DisplayMetrics; import android.util.Log; import android.view.GestureDetector; +import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; @@ -47,6 +48,7 @@ import android.view.inputmethod.InputConnection; import jackpal.androidterm.compat.AndroidCompat; +import jackpal.androidterm.compat.KeyCharacterMapCompat; import jackpal.androidterm.model.TextRenderer; import jackpal.androidterm.model.UpdateCallback; import jackpal.androidterm.session.TerminalEmulator; @@ -898,7 +900,7 @@ public boolean onKeyUp(int keyCode, KeyEvent event) { } } - mKeyListener.keyUp(keyCode); + mKeyListener.keyUp(keyCode, event); clearSpecialKeyStatus(); return true; } @@ -1781,6 +1783,12 @@ class TermKeyListener { private static final int LAST_KEYCODE = KEYCODE_PROG_BLUE; + private static final int META_ALT_ON = 2; + private static final int META_CAPS_LOCK_ON = 0x00100000; + private static final int META_CTRL_ON = 0x1000; + private static final int META_SHIFT_ON = 1; + private static final int META_CTRL_MASK = 0x7000; + private String[] mKeyCodes = new String[256]; private String[] mAppKeyCodes = new String[256]; @@ -1949,8 +1957,6 @@ public boolean isActive() { private ModifierKey mFnKey = new ModifierKey(); - private boolean mCapsLock; - private TermSession mTermSession; private int mBackKeyCode; @@ -1988,8 +1994,12 @@ public void handleFnKey(boolean down) { } public int mapControlChar(int ch) { + return mapControlChar(mControlKey.isActive(), mFnKey.isActive(), ch); + } + + public int mapControlChar(boolean control, boolean fn, int ch) { int result = ch; - if (mControlKey.isActive()) { + if (control) { // Search is the control key. if (result >= 'a' && result <= 'z') { result = (char) (result - 'a' + '\001'); @@ -2014,7 +2024,7 @@ public int mapControlChar(int ch) { } else if (result == '0') { result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_F12; } - } else if (mFnKey.isActive()) { + } else if (fn) { if (result == 'w' || result == 'W') { result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_UP; } else if (result == 'a' || result == 'A') { @@ -2074,41 +2084,58 @@ public void keyDown(int keyCode, KeyEvent event, boolean appMode) throws IOExcep return; } int result = -1; + boolean allowToggle = isEventFromToggleDevice(event); + boolean chordedCtrl = false; switch (keyCode) { case KeyEvent.KEYCODE_ALT_RIGHT: case KeyEvent.KEYCODE_ALT_LEFT: - mAltKey.onPress(); + if (allowToggle) { + mAltKey.onPress(); + } break; case KeyEvent.KEYCODE_SHIFT_LEFT: case KeyEvent.KEYCODE_SHIFT_RIGHT: - mCapKey.onPress(); + if (allowToggle) { + mCapKey.onPress(); + } break; case KEYCODE_CTRL_LEFT: case KEYCODE_CTRL_RIGHT: - mControlKey.onPress(); - break; + // Ignore the control key. + return; case KEYCODE_CAPS_LOCK: - if (event.getRepeatCount() == 0) { - mCapsLock = !mCapsLock; - } - break; + // Ignore the capslock key. + return; case KeyEvent.KEYCODE_BACK: result = mBackKeyCode; break; default: { - result = event.getUnicodeChar( - (mCapKey.isActive() || mCapsLock ? KeyEvent.META_SHIFT_ON : 0) | - (mAltKey.isActive() ? KeyEvent.META_ALT_ON : 0)); + int metaState = event.getMetaState(); + chordedCtrl = ((META_CTRL_ON & metaState) != 0); + boolean effectiveCaps = allowToggle && + (mCapKey.isActive()); + boolean effectiveAlt = allowToggle && mAltKey.isActive(); + int effectiveMetaState = metaState & (~META_CTRL_MASK); + if (effectiveCaps) { + effectiveMetaState |= KeyEvent.META_SHIFT_ON; + } + if (effectiveAlt) { + effectiveMetaState |= KeyEvent.META_ALT_ON; + } + result = event.getUnicodeChar(effectiveMetaState); break; } } - result = mapControlChar(result); + boolean effectiveControl = chordedCtrl || (allowToggle && mControlKey.isActive()); + boolean effectiveFn = allowToggle && mFnKey.isActive(); + + result = mapControlChar(effectiveControl, effectiveFn, result); if (result >= KEYCODE_OFFSET) { handleKeyCode(result - KEYCODE_OFFSET, appMode); @@ -2117,6 +2144,16 @@ public void keyDown(int keyCode, KeyEvent event, boolean appMode) throws IOExcep } } + private boolean isEventFromToggleDevice(KeyEvent event) { + if (AndroidCompat.SDK < 11) { + return true; + } + KeyCharacterMapCompat kcm = KeyCharacterMapCompat.wrap( + KeyCharacterMap.load(event.getDeviceId())); + return kcm.getModifierBehaviour() == + KeyCharacterMapCompat.MODIFIER_BEHAVIOR_CHORDED_OR_TOGGLED; + } + public boolean handleKeyCode(int keyCode, boolean appMode) throws IOException { if (keyCode >= 0 && keyCode < mKeyCodes.length) { String code = null; @@ -2139,20 +2176,25 @@ public boolean handleKeyCode(int keyCode, boolean appMode) throws IOException { * * @param keyCode the keyCode of the keyUp event */ - public void keyUp(int keyCode) { + public void keyUp(int keyCode, KeyEvent event) { + boolean allowToggle = isEventFromToggleDevice(event); switch (keyCode) { case KeyEvent.KEYCODE_ALT_LEFT: case KeyEvent.KEYCODE_ALT_RIGHT: - mAltKey.onRelease(); + if (allowToggle) { + mAltKey.onRelease(); + } break; case KeyEvent.KEYCODE_SHIFT_LEFT: case KeyEvent.KEYCODE_SHIFT_RIGHT: - mCapKey.onRelease(); + if (allowToggle) { + mCapKey.onRelease(); + } break; case KEYCODE_CTRL_LEFT: case KEYCODE_CTRL_RIGHT: - mControlKey.onRelease(); + // ignore control keys. break; default: diff --git a/src/jackpal/androidterm/compat/KeyCharacterMapCompat.java b/src/jackpal/androidterm/compat/KeyCharacterMapCompat.java new file mode 100644 index 000000000..88dad270d --- /dev/null +++ b/src/jackpal/androidterm/compat/KeyCharacterMapCompat.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2011 Jack Palevich + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jackpal.androidterm.compat; + +import android.view.KeyCharacterMap; + +public abstract class KeyCharacterMapCompat { + public static final int MODIFIER_BEHAVIOR_CHORDED = 0; + public static final int MODIFIER_BEHAVIOR_CHORDED_OR_TOGGLED = 1; + + public static KeyCharacterMapCompat wrap(Object map) { + if (map != null) { + if (AndroidCompat.SDK >= 11) { + return new KeyCharacterMapApi11OrLater(map); + } + } + return null; + } + + private static class KeyCharacterMapApi11OrLater + extends KeyCharacterMapCompat { + private KeyCharacterMap mMap; + public KeyCharacterMapApi11OrLater(Object map) { + mMap = (KeyCharacterMap) map; + } + public int getModifierBehaviour() { + return mMap.getModifierBehavior(); + } + } + + public abstract int getModifierBehaviour(); +} From 10d20881b611a99842bbe42046f98218e6381250 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Wed, 25 Jan 2012 17:05:06 -0800 Subject: [PATCH 225/847] Version 1.0.41 --- AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 6569a3e03..625edef1a 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,6 +1,6 @@ From 5a8534deea3d3ec17655135404be2a2d81bfd971 Mon Sep 17 00:00:00 2001 From: EauLand Date: Sun, 29 Jan 2012 01:40:03 +0100 Subject: [PATCH 226/847] Improve 7 Strings, Add 2 Strings and I translate CTRL / Fn Keys --- res/values-fr/strings.xml | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml index d3024d5bb..391d36222 100644 --- a/res/values-fr/strings.xml +++ b/res/values-fr/strings.xml @@ -17,28 +17,30 @@ Terminal Emulator - Préférences + Paramètres Nouvelle fenêtre Fermer fenêtre Fenêtre - Fenêtre Prec. + Fenêtre Préc. Fenêtre Suiv. Terminal par défaut Envoyer par e-mail Touches spéciales Afficher/Masquer Clavier - Etat de cette fenêtre par défaut. + État de cette fenêtre par défaut. - Activer WakeLock - Désactiver WakeLock - Activer WifiLock - Désactiver WifiLock + Bloquer la mise en veille + Débloquer la mise en veille + Bloquer le Wifi + Débloquer le Wifi Modifier le texte Selectionner le texte Tout copier Coller + Touche Control + Touche Fonction Fenêtre %1$d @@ -54,7 +56,7 @@ Barre de statut Barre d\'actions - Choisir le comportement de la barre d\'actions (A partir d\'Android 3.0) + Choisir le comportement de la barre d\'actions (À partir d\'Android 3.0) Barre d\'actions Style du curseur @@ -113,6 +115,12 @@ Quitter le terminal en exécutant la commande exit Touches Control et Fonction + + CTRLKEY Space : Control-@ (NUL)\nCTRLKEY A..Z : Control-A..Z\nCTRLKEY 5 : Control-]\nCTRLKEY 6 : Control-^\nCTRLKEY 7 : Control-_\nCTRLKEY 9 : F11\nCTRLKEY 0 : F12 + Touche Control non paramètrées. + + FNKEY 1..9 : F1-F9\nFNKEY 0 : F10\nFNKEY W : Haut\nFNKEY A : Gauche\nFNKEY S : Bas\nFNKEY D : Droite\nFNKEY P : PageHaut\nFNKEY N : PageBas\nFNKEY T : Tab\nFNKEY L : | (pipe)\nFNKEY U : _ (underscore)\nFNKEY E : Control-[ (ESC)\nFNKEY X : Suppr.\nFNKEY I : Inser.\nFNKEY H : Début\nFNKEY F : Fin\nFNKEY . : Control-\\\n + Touche fonction non paramètrées. Fermer cette fenêtre ? From d94299476250a0b736ecd3a0811495df62617561 Mon Sep 17 00:00:00 2001 From: Ondrej Zima Date: Mon, 6 Feb 2012 11:57:49 +0100 Subject: [PATCH 227/847] Czech: added missing strings The function key translation is hard to achieve so I try to do it this way and after I get the translated package I look at it again to correct it. Change-Id: I7418f61c497a38378444a150d788acfee1c2fc47 --- res/values-cs/arrays.xml | 28 ++++++++++++++++++++++++++-- res/values-cs/strings.xml | 11 +++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/res/values-cs/arrays.xml b/res/values-cs/arrays.xml index 8b04a973e..e158612a7 100644 --- a/res/values-cs/arrays.xml +++ b/res/values-cs/arrays.xml @@ -61,7 +61,8 @@ Klávesa pravý Alt Klávesa Hlas+ Klávesa Hlas- - Klávesa kamery + Klávesa fotoaparátu + Žádná @@ -71,11 +72,34 @@ Klávesa pravý Alt Klávesa Hlas+ Klávesa Hlas- - Klávesa kamery + Klávesa fotoaparátu + Žádná Po znacích Po slovech + + + Kulička + \@ + Levý Alt + Pravý Alt + Hlas+ + Hlas- + Fotoaparát + Žádná + + + + Kulička + \@ + Levý Alt + Pravý Alt + Hlas+ + Hlas- + Fotoaparát + Žádná + diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml index 5521ca932..d5ce44a60 100644 --- a/res/values-cs/strings.xml +++ b/res/values-cs/strings.xml @@ -17,6 +17,11 @@ Emulace terminálu Nastavení + Nové okno + Zavřít okno + Okna + Další okno + Předchozí okno Obnovit term. Poslat e-mailem Spec. klávesy @@ -80,4 +85,10 @@ Inicializace Provést po spuštění shellu. Inicializace + + Ctrl a funkční klávesy + CTRLKEY mezera : Ctrl-@ (NUL)\nCTRLKEY A..Z : Ctrl-A..Z\nCTRLKEY 5 : Ctrl-]\nCTRLKEY 6 : Ctrl-^\nCTRLKEY 7 : Ctrl-_\nCTRLKEY 9 : F11\nCTRLKEY 0 : F12 + Ctrl klávesy nenastaveny. + FNKEY 1..9 : F1-F9\nFNKEY 0 : F10\nFNKEY W : nahoru\nFNKEY A : doleva\nFNKEY S : dolů\nFNKEY D : dopravat\nFNKEY P : stránka nahoru\nFNKEY N : stránka dolů\nFNKEY T : tabulátor\nFNKEY L : | (pipe)\nFNKEY U : _ (podtržítko)\nFNKEY E : Ctrl-[ (ESC)\nFNKEY X : smazat\nFNKEY I : vložit\nFNKEY H : domů\nFNKEY F : konec\nFNKEY . : Ctrl-\\\n + Funkční klávesy nenastaveny. From e6bb663c693debce45e9d5c9514ef62bf9eda4d2 Mon Sep 17 00:00:00 2001 From: Danny Baumann Date: Wed, 8 Feb 2012 10:19:40 +0100 Subject: [PATCH 228/847] Update German translation. Change-Id: I638e59a9190d54dcbb14ceb10ac08d109ae54ec4 --- res/values-de/arrays.xml | 28 +++++++++++++++++++++++++--- res/values-de/strings.xml | 15 +++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/res/values-de/arrays.xml b/res/values-de/arrays.xml index d76607a6e..67d3631fe 100644 --- a/res/values-de/arrays.xml +++ b/res/values-de/arrays.xml @@ -46,9 +46,9 @@ - Schwarzer Text auf weiss - Weisser Text auf schwarz - Weisser Text auf blau + Schwarzer Text auf weiß + Weißer Text auf schwarz + Weißer Text auf blau Grüner Text auf schwarz Oranger Text auf schwarz Roter Text auf schwarz @@ -78,4 +78,26 @@ Zeichen-basiert Wort-basiert + + + Ball + \@ + Alt-L + Alt-R + Lauter + Leiser + Kamera + Kein + + + + Ball + \@ + Alt-L + Alt-R + Lauter + Leiser + Camera + Kein + diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml index e76ded98b..f46b53651 100644 --- a/res/values-de/strings.xml +++ b/res/values-de/strings.xml @@ -17,6 +17,11 @@ Terminal Emulator Einstellungen + Neues Fenster + Fenster schließen + Fenster + Vorheriges Fenster + Nächstes Fenster Zurücksetzen E-Mail an Spezialtasten @@ -81,4 +86,14 @@ Startkommando Kommando eingeben, dass beim Start an die Shell gesendet wird. Startkommando + + Steuer- und Funktionstasten + + CTRLKEY Leertaste : Strg-@ (NUL)\nCTRLKEY A..Z : Strg-A..Z\nCTRLKEY 5 : Strg-]\nCTRLKEY 6 : Strg-^\nCTRLKEY 7 : Strg-_\nCTRLKEY 9 : F11\nCTRLKEY 0 : F12 + Keine Steuertasten gesetzt. + + FNKEY 1..9 : F1-F9\nFNKEY 0 : F10\nFNKEY W : Oben\nFNKEY A : Links\nFNKEY S : Unten\nFNKEY D : Rechts\nFNKEY P : Seite nach oben\nFNKEY N : Seite nach unten\nFNKEY T : Tab\nFNKEY L : | (Pipe)\nFNKEY U : _ (Unterstrich)\nFNKEY E : Escape\nFNKEY X : Entfernen\nFNKEY I : Einfügen\nFNKEY H : Home\nFNKEY F : Ende\nFNKEY . : Strg-\\\n + Keine Funktionstasten gesetzt. From 302705f34c68acba7b2f2519dc7c3f30c30e4801 Mon Sep 17 00:00:00 2001 From: pvolkov Date: Sat, 18 Feb 2012 15:08:47 +0400 Subject: [PATCH 229/847] Update Russian translation - Android Terminal Update Russian translation - removed trailing whitespace --- res/values-ru/arrays.xml | 30 ++++++++++++++++++++++++++++-- res/values-ru/strings.xml | 10 ++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/res/values-ru/arrays.xml b/res/values-ru/arrays.xml index a566e011b..778e97398 100644 --- a/res/values-ru/arrays.xml +++ b/res/values-ru/arrays.xml @@ -44,13 +44,14 @@ - Jog ball + Трекбол Клавиша \@ Левый Alt Правый Alt Громкость вверх Громкость вниз Кнопка камеры + Нет @@ -70,13 +71,38 @@ - Jog ball + Трекбол Клавиша \@ Левый Alt Правый Alt Громкость вверх Громкость вниз Кнопка камеры + Нет + + + + + Трекбол + Клавиша \@ + Левый Alt + Правый Alt + Громкость вверх + Громкость вниз + Кнопка камеры + Нет + + + + + Трекбол + Клавиша \@ + Левый Alt + Правый Alt + Громкость вверх + Громкость вниз + Кнопка камеры + Нет diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml index 2a46f78c2..e990e5c13 100644 --- a/res/values-ru/strings.xml +++ b/res/values-ru/strings.xml @@ -17,6 +17,11 @@ Эмулятор Терминала Настройки + Новое окно + Закрыть окно + Окна + Пред. окно + След. окно Сбросить терм. Отправит Email Специальные клавиши @@ -64,4 +69,9 @@ Команды запуска Передаются облочке при запуске терминала. Команды запуска + + Клавиши Control и Fn + Клавиша Control не установлена. + Клавиша Fn не установлена. + From 104425131581df1e627c36189c1e4ce144a69cf0 Mon Sep 17 00:00:00 2001 From: Marco Brohet Date: Thu, 1 Mar 2012 12:53:34 +0100 Subject: [PATCH 230/847] AndroidTerm: Updated Dutch translations Change-Id: Ib4e99a6f0924901602f34b782f0e2a0a56b387f1 --- res/values-nl/arrays.xml | 70 +++++++++++++++++++++++++++++---------- res/values-nl/strings.xml | 60 +++++++++++++++++++++------------ 2 files changed, 91 insertions(+), 39 deletions(-) diff --git a/res/values-nl/arrays.xml b/res/values-nl/arrays.xml index e8a60780f..9c0d38814 100644 --- a/res/values-nl/arrays.xml +++ b/res/values-nl/arrays.xml @@ -17,7 +17,7 @@ - Geef statusbalk weer + Toon statusbalk Verberg statusbalk @@ -34,15 +34,15 @@ 4 x 8 pixels - 6 pt - 7 pt - 8 pt - 9 pt - 10 pt - 12 pt - 14 pt - 16 pt - 20 pt + 6 pt. + 7 pt. + 8 pt. + 9 pt. + 10 pt. + 12 pt. + 14 pt. + 16 pt. + 20 pt. @@ -56,16 +56,50 @@ Trackball - \@ knop - Linker Alt knop - Rechter Alt knop - Volume omhoog knop - Volume omlaag knop - Camera knop + \@-knop + Linker Alt-knop + Rechter Alt-knop + Volume omhoog-knop + Volume omlaag-knop + Cameraknop + Geen + + + + Trackball + \@-knop + Linker Alt-knop + Rechter Alt-knop + Volume omhoog-knop + Volume omlaag-knop + Cameraknop + Geen - Karakter gebasseerd - Woord gebasseerd + Karaktergebaseerd + Woordgebaseerd + + + + Ball + \@ + Alt-links + Alt-rechts + Volume+ + Volume- + Camera + Geen + + + + Ball + \@ + Alt-links + Alt-rechts + Volume+ + Volume- + Camera + Geen diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml index 445a9fa51..e41fc6e9b 100644 --- a/res/values-nl/strings.xml +++ b/res/values-nl/strings.xml @@ -15,42 +15,54 @@ * limitations under the License. --> - Terminal Emulator + Opdrachtprompt Instellingen - Reset + Nieuw venster + Sluit venster + Vensters + Vorige venster + Volgende venster + Herstellen Email naar Speciale knoppen Zet toetsenbord aan/uit - Pas tekst aan + Slaapblokkering aan + Slaapblokkering uit + Wi-Fi-blokkering aan + Wi-Fi-blokkering uit + + Wijzig tekst Selecteer tekst Alles kopiëren Plakken + Terminalsessie is bezig + Scherm Statusbalk - Statusbalk weergeven/verbergen. + Statusbalk tonen/verbergen. Statusbalk - Cursor stijl - Kies cursor stijl. - Cursor stijl + Cursorstijl + Kies cursorstijl. + Cursorstijl - Cursor blink - Kies cursor blink. - Cursor blink + Cursorblink + Kies cursorblink. + Cursorblink Tekst Lettergrootte - Kies karakter hoogte in punten. + Kies karakterhoogte in punten. Lettergrootte Kleuren - Kies tekst kleur. - Tekst kleur + Kies tekstkleur. + Tekstkleur Toetsenbord @@ -58,16 +70,22 @@ Kies bedieningsknop. Bedieningsknop - Invoer methode - Kies invoer methode voor toetsenbord. - Invoer methode + Fn-knop + Kies Fn-knop. + Fn-knop + + Invoermethode + Kies invoermethode voor toetsenbord. + Invoermethode Shell - Command line - Specificeer de shell command line. + Opdrachtregel + Geef opdrachtregel-shell op Shell - Oorspronkelijke opdracht - Wordt naar de shell verzonden wanneer deze gestart wordt. - Oorspronkelijke opdracht + Beginopdracht + Wordt naar de shell verzonden bij het opstarten. + Beginopdracht + + Bedienings- en functieknoppen From 932181fcc224152500f7008bca05e1e9e5e9a9df Mon Sep 17 00:00:00 2001 From: corenting Date: Mon, 30 Jan 2012 21:12:54 +0100 Subject: [PATCH 231/847] Translated locks (fr). Signed-off-by: corenting Change-Id: If88ca977b485c7b05f7dabe604f3da829015acf8 --- res/values-fr/strings.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml index 5c3de7e6b..3b9f0aa3a 100644 --- a/res/values-fr/strings.xml +++ b/res/values-fr/strings.xml @@ -21,11 +21,11 @@ Envoyer par e-mail Touches spéciales Afficher/Masquer Clavier - - Activer WakeLock - Désactiver WakeLock - Activer WifiLock - Désactiver WifiLock + + Prendre WakeLock + Lâcher WakeLock + Prendre WifiLock + Lâcher WifiLock Modifier le texte Selectionner le texte From a25ac82d2ee62c75ac3a90651d387d2f7001f66d Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 10 Mar 2012 11:27:33 -0800 Subject: [PATCH 232/847] Replace ad-hoc intent script execution with more formal interface The current intent script execution mechanism has at least two serious problems: * If a Term activity is already running, it will just bring the activity to the top, without opening a new window or running the provided script. * It allows any other application to use our permissions, even if they don't have those permissions themselves. Replace the current ad-hoc mechanism with a more formally defined remote intent interface, which provides two actions: * jackpal.androidterm.OPEN_NEW_WINDOW opens a new terminal window. No script execution is allowed, and no permissions are required to use this action. * jackpal.androidterm.RUN_SCRIPT opens a new window and runs the script specified in the jackpal.androidterm.iInitialCommand extra. Applications using this intent must have the jackpal.androidterm.permission.RUN_SCRIPT permission, which must be approved by the user at install time. Signed-off-by: Jack Palevich --- AndroidManifest.xml | 20 ++++ res/values/strings.xml | 5 +- src/jackpal/androidterm/RemoteInterface.java | 112 +++++++++++++++++++ src/jackpal/androidterm/Term.java | 27 +++-- 4 files changed, 151 insertions(+), 13 deletions(-) create mode 100644 src/jackpal/androidterm/RemoteInterface.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 625edef1a..80518944d 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -5,6 +5,11 @@ + + + + + + + + + + + + + Email to Special keys Toggle soft keyboard - + This window\'s terminal state has been reset. Take WakeLock @@ -124,4 +124,7 @@ No function key set. Close this window? + + Run arbitrary commands in Terminal Emulator + Allows application to open new windows in Android Terminal Emulator and run commands in those windows with all of Android Terminal Emulator\'s permissions, including access to the Internert and your SD Card. diff --git a/src/jackpal/androidterm/RemoteInterface.java b/src/jackpal/androidterm/RemoteInterface.java new file mode 100644 index 000000000..f94e9e5bd --- /dev/null +++ b/src/jackpal/androidterm/RemoteInterface.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2012 Steven Luo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jackpal.androidterm; + +import android.app.Activity; +import android.content.ComponentName; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.os.IBinder; +import android.preference.PreferenceManager; +import android.util.Log; + +import jackpal.androidterm.session.TermSession; +import jackpal.androidterm.util.TermSettings; + +public class RemoteInterface extends Activity { + private static final String ACTION_OPEN_NEW_WINDOW = "jackpal.androidterm.OPEN_NEW_WINDOW"; + private static final String ACTION_RUN_SCRIPT = "jackpal.androidterm.RUN_SCRIPT"; + + public static final String EXTRA_REMOTE_OPEN_WINDOW = "jackpal.androidterm.remote_open_window"; + private static final String EXTRA_INITIAL_COMMAND = "jackpal.androidterm.iInitialCommand"; + + private TermSettings mSettings; + + private TermService mTermService; + private ServiceConnection mTSConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + TermService.TSBinder binder = (TermService.TSBinder) service; + mTermService = binder.getService(); + handleIntent(); + } + + public void onServiceDisconnected(ComponentName className) { + mTermService = null; + } + }; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + mSettings = new TermSettings(getResources(), prefs); + + Intent TSIntent = new Intent(this, TermService.class); + startService(TSIntent); + if (!bindService(TSIntent, mTSConnection, BIND_AUTO_CREATE)) { + Log.e(TermDebug.LOG_TAG, "bind to service failed!"); + finish(); + } + } + + private void handleIntent() { + TermService service = mTermService; + if (service == null) { + finish(); + return; + } + + Intent myIntent = getIntent(); + if (myIntent.getAction().equals(ACTION_RUN_SCRIPT)) { + /* Someone with the appropriate permissions has asked us to + run a script */ + openNewWindow(myIntent.getStringExtra(EXTRA_INITIAL_COMMAND)); + } else { + // Intent sender may not have permissions, ignore any extras + openNewWindow(null); + } + } + + private void openNewWindow(String iInitialCommand) { + TermService service = mTermService; + + String initialCommand = mSettings.getInitialCommand(); + if (iInitialCommand != null) { + if (initialCommand != null) { + initialCommand += "\r" + iInitialCommand; + } else { + initialCommand = iInitialCommand; + } + } + + TermSession session = new TermSession(mSettings, service, initialCommand); + service.getSessions().add(session); + + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.setComponent(new ComponentName(this, Term.class)); + intent.addCategory(Intent.CATEGORY_LAUNCHER); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra(EXTRA_REMOTE_OPEN_WINDOW, true); + startActivity(intent); + + unbindService(mTSConnection); + finish(); + } +} diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index 20ada3f56..3c7841b55 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -340,19 +340,7 @@ private void restart() { } private TermSession createTermSession() { - /* Check whether we've received an initial command from the - * launching application - */ String initialCommand = mSettings.getInitialCommand(); - String iInitialCommand = getIntent().getStringExtra("jackpal.androidterm.iInitialCommand"); - if (iInitialCommand != null) { - if (initialCommand != null) { - initialCommand += "\r" + iInitialCommand; - } else { - initialCommand = iInitialCommand; - } - } - return new TermSession(mSettings, mTermService, initialCommand); } @@ -628,6 +616,21 @@ protected void onActivityResult(int request, int result, Intent data) { } } + @Override + protected void onNewIntent(Intent intent) { + if (intent.getBooleanExtra(RemoteInterface.EXTRA_REMOTE_OPEN_WINDOW, false)) { + // New session was created, add an EmulatorView to match + SessionList sessions = mTermSessions; + int position = sessions.size() - 1; + + TermSession session = sessions.get(position); + EmulatorView view = createEmulatorView(session); + + mViewFlipper.addView(view); + onResumeSelectWindow = position; + } + } + @Override public boolean onPrepareOptionsMenu(Menu menu) { MenuItem wakeLockItem = menu.findItem(R.id.menu_toggle_wakelock); From eec1db809ce98589362f29434a5a7c077253a18c Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sun, 11 Mar 2012 10:17:54 -0700 Subject: [PATCH 233/847] Add a localization for CZ - Czech Republic Thanks to Jozka.1@seznam.cz --- res/values-cz/arrays.xml | 217 ++++++++++++++++++++++++++++++++++++++ res/values-cz/strings.xml | 127 ++++++++++++++++++++++ 2 files changed, 344 insertions(+) create mode 100644 res/values-cz/arrays.xml create mode 100644 res/values-cz/strings.xml diff --git a/res/values-cz/arrays.xml b/res/values-cz/arrays.xml new file mode 100644 index 000000000..48b2b1d8c --- /dev/null +++ b/res/values-cz/arrays.xml @@ -0,0 +1,217 @@ + + + + + + Zobrazovat stavový řádek + Nebrazovat stavový řádek + + + + + 1 + 0 + + + + + Vždy zobrazovat panel akcí + Skrývat panel akcí (dotekem v horní části obrazovky, nebo tlačítkem menu) + + + + + + 1 + 2 + + + + Neblikající kurzor \:\) + Blikající kurzor + + + + + 0 + 1 + + + + Obdelník + Podtržítko + Svislá čára + + + + + 0 + 1 + 2 + + + + 4 x 8 pixelů + 6 pt + 7 pt + 8 pt + 9 pt + 10 pt + 12 pt + 14 pt + 16 pt + 20 pt + + + + + 0 + 6 + 7 + 8 + 9 + 10 + 12 + 14 + 16 + 20 + + + + Černý text na bílém podkladu + Bílý text na černém podkladu + Bílý text na modrém podkladu + Zelený text na černém podkladu + Žlutý text na černém podkladu + Červený text na černém podkladu + + + + + 0 + 1 + 2 + 3 + 4 + 5 + + + + Zavřít všechna terminálová okna + Zavřít pouze toto terminálové okno + Zavřít terminál, ale dál poběží na pozadí + Poslat klávesu ESC do terminálu + Poslat klávesu TAB do terminálu + + + + + 0 + 1 + 2 + 3 + 4 + + + + Pohybové tlačítko + Tlačítko \@ + Levá klávesa ALT + Pravá klávesa ALT + Tlačítko hlasitosti dolů + Tlačítko hlasitosti nahoru + Tlačítko kamery + Žádné + + + + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + + + + Pohybové tlačítko + Tlačítko \@ + Levá klávesa ALT + Pravá klávesa ALT + Tlačítko hlasitosti dolů + Tlačítko hlasitosti nahoru + Tlačítko kamery + Žádné + + + + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + + + + Zadávání po znacích + Zadávání po slovech + + + + + 0 + 1 + + + + + Kulička + \@ + Levý Alt + Pravý Alt + Vol-Up + Vol-Dn + Kamera + Žádné + + + + + Kulička + \@ + Levý Alt + Pravý Alt + Vol-Up + Vol-Dn + Kamera + Žádné + + + + + vt100 + screen + linux + + \ No newline at end of file diff --git a/res/values-cz/strings.xml b/res/values-cz/strings.xml new file mode 100644 index 000000000..f3d19bcce --- /dev/null +++ b/res/values-cz/strings.xml @@ -0,0 +1,127 @@ + + + + Terminal Emulator + Nastavení + Nové okno + Zavřít okno + Okna + Predešlé okno + Další okno + Reset terminálu + Napiš email + Speciální znaky + Přepnout soft. klávesnici + + Toto terminálové okno bylo restartováno. + + Zabránit uspání + Povolit uspání + Zabránit odpojení wifi + Povolit odpojení wifi + + Práce s textem + Vybrat text + Kopírovat vše + Vložit + Pošli klávesu control + Pošli klávesu fn + + Okno %1$d + + Terminál je spuštěný + + Terminál je ukončen + + + Obrazovka + + Stavový řádek + Zobrazit/skrýt stavový řádek. + Stavový řádek + + Panel akcí + Vyberte chování panelu akcí (Android 3.0 a novější). + Chování panelu akcí + + Styl kurzoru + Vyberte styl kurzoru. + Styl kurzoru + + Blikání kurzoru + Vyberte chování kurzoru. + Blikání kurzoru + + Text + + Výchozí formát UTF-8 + Nastavte formátování UTF-8 jako výchozí. + + Velikost písma + Vyberte velikost písma. + Velikost písma + + Barvy + Vyberte barevné schéma terminálu. + Barva textu + + Klávesnice + + Chování tlačítka zpět + Vyberte chování tlačítka zpět. + Chování tlačítka zpět + + Klávesa control + Vyberte tlačítko pro klávesu control. + Klávesa control + + Klávesa Fn + Vyberte tlačítko pro klávesu Fn. + Klávesa Fn + + Vstupní metoda + Vyberte vstupní metodu pro soft. klávesnici. + Vstupní metoda + + Shell + Příkazový řádek + Nastavte příkazový řádek. + Shell + + Počáteční příkaz + Nastavte počáteční příkaz po spuštění terminálu. + Počáteční příkaz + + Typ terminálu + Vyberte typ terminálu pro interpretaci příkazů. + Typ terminálu + + Zavřít okno při ukončení + Nastavte, zda má být okno zavřeno po ukončení shellu. + + Funkční klávesy a klávesa control + + CTRLKEY Space : Control-@ (NUL)\nCTRLKEY A..Z : Control-A..Z\nCTRLKEY 5 : Control-]\nCTRLKEY 6 : Control-^\nCTRLKEY 7 : Control-_\nCTRLKEY 9 : F11\nCTRLKEY 0 : F12 + Není nastaveno žádné tlačitko pro control. + + FNKEY 1..9 : F1-F9\nFNKEY 0 : F10\nFNKEY W : Up\nFNKEY A : Left\nFNKEY S : Down\nFNKEY D : Right\nFNKEY P : PageUp\nFNKEY N : PageDown\nFNKEY T : Tab\nFNKEY L : | (pipe)\nFNKEY U : _ (underscore)\nFNKEY E : Control-[ (ESC)\nFNKEY X : Delete\nFNKEY I : Insert\nFNKEY H : Home\nFNKEY F : End\nFNKEY . : Control-\\\n + Není nastaveno žádné tlačitko pro funkční klávesu. + + Zavřít okno? + \ No newline at end of file From 0e55c7f25a61e453b6f14e444145b4e3ebb7a696 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sun, 11 Mar 2012 14:22:21 -0700 Subject: [PATCH 234/847] Fix spelling mistake. --- res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index dae39e6f1..3c6f9982f 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -126,5 +126,5 @@ Close this window? Run arbitrary commands in Terminal Emulator - Allows application to open new windows in Android Terminal Emulator and run commands in those windows with all of Android Terminal Emulator\'s permissions, including access to the Internert and your SD Card. + Allows application to open new windows in Android Terminal Emulator and run commands in those windows with all of Android Terminal Emulator\'s permissions, including access to the Internet and your SD Card. From be0e0e1910479a2f119500b4ac34054303f5dd96 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sun, 25 Mar 2012 18:13:53 -0700 Subject: [PATCH 235/847] Add proguard-project.txt file Doesn't do anything currently, we may start using proguard in the future to save a few bytes in the apk. --- proguard-project.txt | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 proguard-project.txt diff --git a/proguard-project.txt b/proguard-project.txt new file mode 100644 index 000000000..f2fe1559a --- /dev/null +++ b/proguard-project.txt @@ -0,0 +1,20 @@ +# To enable ProGuard in your project, edit project.properties +# to define the proguard.config property as described in that file. +# +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in ${sdk.dir}/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the ProGuard +# include property in project.properties. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} From ce9557eb3c41fbb38a24c56954d1d2e2f809e588 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sun, 25 Mar 2012 18:14:12 -0700 Subject: [PATCH 236/847] Update build script for NDK version 7b --- tools/build-release | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/build-release b/tools/build-release index 08ed926b6..a0095cb37 100755 --- a/tools/build-release +++ b/tools/build-release @@ -7,4 +7,4 @@ cd ~/code/Android-Terminal-Emulator rm -rf bin obj cd jni -~/code/android-ndk-r6b/ndk-build && cd .. && ant release && mv bin/Term-release.apk bin/Term.apk +~/code/android-ndk-r7b/ndk-build && cd .. && ant release && mv bin/Term-release.apk bin/Term.apk From 2f06c89e67fed7c19d921a7762a4a95fe2c9a358 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Wed, 21 Mar 2012 03:00:05 -0700 Subject: [PATCH 237/847] Deal with null row color when getting transcript with colors Signed-off-by: Jack Palevich --- .../androidterm/session/TranscriptScreen.java | 22 +++++++++++++------ .../androidterm/util/UnicodeTranscript.java | 4 ++++ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/jackpal/androidterm/session/TranscriptScreen.java b/src/jackpal/androidterm/session/TranscriptScreen.java index 64bd2cfd9..bc4b96bef 100644 --- a/src/jackpal/androidterm/session/TranscriptScreen.java +++ b/src/jackpal/androidterm/session/TranscriptScreen.java @@ -371,13 +371,21 @@ private String internalGetTranscriptText(StringBuilder colors, int selX1, int se builder.append(line, 0, lastPrintingChar + 1); if (colors != null) { int column = 0; - for (int j = 0; j < lastPrintingChar + 1; ++j) { - colors.append((char) rowColorBuffer[column]); - if (Character.isHighSurrogate(line[j])) { - column += UnicodeTranscript.charWidth(Character.toCodePoint(line[j], line[j+1])); - ++j; - } else { - column += UnicodeTranscript.charWidth(line[j]); + if (rowColorBuffer != null) { + for (int j = 0; j < lastPrintingChar + 1; ++j) { + colors.append((char) rowColorBuffer[column]); + if (Character.isHighSurrogate(line[j])) { + column += UnicodeTranscript.charWidth( + Character.toCodePoint(line[j], line[j+1])); + ++j; + } else { + column += UnicodeTranscript.charWidth(line[j]); + } + } + } else { + char defaultColor = (char) mData.getDefaultColorsEncoded(); + for (int j = 0; j < lastPrintingChar + 1; ++j) { + colors.append(defaultColor); } } } diff --git a/src/jackpal/androidterm/util/UnicodeTranscript.java b/src/jackpal/androidterm/util/UnicodeTranscript.java index ce7769fab..07c3973f7 100644 --- a/src/jackpal/androidterm/util/UnicodeTranscript.java +++ b/src/jackpal/androidterm/util/UnicodeTranscript.java @@ -88,6 +88,10 @@ public int getDefaultBackColor() { return mDefaultBackColor; } + public byte getDefaultColorsEncoded() { + return encodeColor(mDefaultForeColor, mDefaultBackColor); + } + public int getActiveTranscriptRows() { return mActiveTranscriptRows; } From 0ebc55b1d64d501fd959a402e191413f5023d5aa Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Wed, 21 Mar 2012 03:00:11 -0700 Subject: [PATCH 238/847] Move some getChar() logic from UnicodeTranscript into FullUnicodeLine Signed-off-by: Jack Palevich --- .../androidterm/util/UnicodeTranscript.java | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/jackpal/androidterm/util/UnicodeTranscript.java b/src/jackpal/androidterm/util/UnicodeTranscript.java index 07c3973f7..76f79ab6b 100644 --- a/src/jackpal/androidterm/util/UnicodeTranscript.java +++ b/src/jackpal/androidterm/util/UnicodeTranscript.java @@ -691,19 +691,7 @@ public boolean getChar(int row, int column, int charIndex, char[] out, int offse } FullUnicodeLine line = (FullUnicodeLine) mLines[row]; - char[] rawLine = line.getLine(); - int pos = line.findStartOfColumn(column); - int length; - if (column + 1 < mColumns) { - length = line.findStartOfColumn(column + 1) - pos; - } else { - length = line.getSpaceUsed() - pos; - } - if (charIndex >= length) { - throw new IllegalArgumentException(); - } - out[offset] = rawLine[pos + charIndex]; - return (charIndex + 1 < length); + return line.getChar(column, charIndex, out, offset); } public int getForeColor(int row, int column) { @@ -898,6 +886,21 @@ public int findStartOfColumn(int column) { } } + public boolean getChar(int column, int charIndex, char[] out, int offset) { + int pos = findStartOfColumn(column); + int length; + if (column + 1 < mColumns) { + length = findStartOfColumn(column + 1) - pos; + } else { + length = getSpaceUsed() - pos; + } + if (charIndex >= length) { + throw new IllegalArgumentException(); + } + out[offset] = mText[pos + charIndex]; + return (charIndex + 1 < length); + } + public void setChar(int column, int codePoint) { int columns = mColumns; if (column < 0 || column >= columns) { From e9e0901e8403f1fa07286355773f5740e84f4102 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Wed, 21 Mar 2012 03:00:28 -0700 Subject: [PATCH 239/847] Handle C1 control characters other than CSI Each C1 control character (U+0080-U+009F) maps to a corresponding seven-bit-clean escape sequence (ESC U+0040-U+005F). We already handle most of these escape sequences, so instead of just handling CSI (U+009B) in an ad-hoc fashion, handle all the C1 controls systematically. Signed-off-by: Jack Palevich --- .../androidterm/session/TerminalEmulator.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/jackpal/androidterm/session/TerminalEmulator.java b/src/jackpal/androidterm/session/TerminalEmulator.java index 35c9c731d..3f3122c23 100644 --- a/src/jackpal/androidterm/session/TerminalEmulator.java +++ b/src/jackpal/androidterm/session/TerminalEmulator.java @@ -520,6 +520,15 @@ private void process(byte b) { return; } + // Handle C1 control characters + if ((b & 0x80) == 0x80 && (b & 0x7f) <= 0x1f) { + /* ESC ((code & 0x7f) + 0x40) is the two-byte escape sequence + corresponding to a particular C1 code */ + startEscapeSequence(ESC); + process((byte) ((b & 0x7f) + 0x40), false); + return; + } + switch (b) { case 0: // NUL // Do nothing @@ -570,10 +579,6 @@ private void process(byte b) { startEscapeSequence(ESC); break; - case (byte) 0x9b: // CSI - startEscapeSequence(ESC_LEFT_SQUARE_BRACKET); - break; - default: mContinueSequence = false; switch (mEscapeState) { From 14c7cebbafa3c8806252db3830dad478e33f5113 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Wed, 21 Mar 2012 03:00:40 -0700 Subject: [PATCH 240/847] Properly handle UTF-8 sequences decoding to C1 control characters Applications which emit UTF-8 sequences that decode to C1 control characters expect these sequences to be interpreted as C1 control characters, so send them back through process() instead of trying to emit them. Signed-off-by: Jack Palevich --- .../androidterm/session/TerminalEmulator.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/jackpal/androidterm/session/TerminalEmulator.java b/src/jackpal/androidterm/session/TerminalEmulator.java index 3f3122c23..fa1d115f6 100644 --- a/src/jackpal/androidterm/session/TerminalEmulator.java +++ b/src/jackpal/androidterm/session/TerminalEmulator.java @@ -515,8 +515,12 @@ public void append(byte[] buffer, int base, int length) { } private void process(byte b) { + process(b, true); + } + + private void process(byte b, boolean doUTF8) { // Let the UTF-8 decoder try to handle it if we're in UTF-8 mode - if (mUTF8Mode && handleUTF8Sequence(b)) { + if (doUTF8 && mUTF8Mode && handleUTF8Sequence(b)) { return; } @@ -654,7 +658,15 @@ private boolean handleUTF8Sequence(byte b) { decoder.reset(); decoder.decode(byteBuf, charBuf, true); decoder.flush(charBuf); - emit(charBuf.array()); + + char[] chars = charBuf.array(); + if (chars[0] >= 0x80 && chars[0] <= 0x9f) { + /* Sequence decoded to a C1 control character which needs + to be sent through process() again */ + process((byte) chars[0], false); + } else { + emit(chars); + } byteBuf.clear(); charBuf.clear(); From c4b50ab98ce60a551d2f72876c31c0fecde037cb Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Wed, 21 Mar 2012 03:00:46 -0700 Subject: [PATCH 241/847] Fix emission of combining characters following wide characters in transcript At the moment, combining characters which follow an East Asian wide character are not being stored in the same column as the characters they modify. Fix this by keeping track of the width of the last spacing mark emitted and using this to place combining characters into the correct column. Signed-off-by: Jack Palevich --- .../androidterm/session/TerminalEmulator.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/jackpal/androidterm/session/TerminalEmulator.java b/src/jackpal/androidterm/session/TerminalEmulator.java index fa1d115f6..e2cfb305a 100644 --- a/src/jackpal/androidterm/session/TerminalEmulator.java +++ b/src/jackpal/androidterm/session/TerminalEmulator.java @@ -233,6 +233,12 @@ public class TerminalEmulator { */ private boolean mAboutToAutoWrap; + /** + * The width of the last emitted spacing character. Used to place + * combining characters into the correct column. + */ + private int mLastEmittedCharWidth = 0; + /** * True if we just auto-wrapped and no character has been emitted on this * line yet. Used to ensure combining characters following a character @@ -1430,9 +1436,9 @@ private void emit(int c, int foreColor, int backColor) { if (width == 0) { // Combining character -- store along with character it modifies if (mJustWrapped) { - mScreen.set(mColumns - 1, mCursorRow - 1, c, foreColor, backColor); + mScreen.set(mColumns - mLastEmittedCharWidth, mCursorRow - 1, c, foreColor, backColor); } else { - mScreen.set(mCursorCol - 1, mCursorRow, c, foreColor, backColor); + mScreen.set(mCursorCol - mLastEmittedCharWidth, mCursorRow, c, foreColor, backColor); } } else { mScreen.set(mCursorCol, mCursorRow, c, foreColor, backColor); @@ -1444,6 +1450,9 @@ private void emit(int c, int foreColor, int backColor) { } mCursorCol = Math.min(mCursorCol + width, mColumns - 1); + if (width > 0) { + mLastEmittedCharWidth = width; + } } private void emit(int c) { From c5b4ec402fb96f1df1e83e22cef16785035aae90 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Wed, 21 Mar 2012 03:00:56 -0700 Subject: [PATCH 242/847] Revise screen resize check handling Move the screen resize check code out of EmulatorView and into TermViewFlipper, where it logically fits now that there are multiple EmulatorViews per Term activity. While we're at it, instead of making the activity tell us how much of the window doesn't belong to us, use getGlobalVisibleRect() to figure that out ourselves, eliminating the need for a WindowSizeCallback. Signed-off-by: Jack Palevich --- src/jackpal/androidterm/EmulatorView.java | 75 +--------- src/jackpal/androidterm/Term.java | 23 +-- src/jackpal/androidterm/TermViewFlipper.java | 149 ++++++++++++++++++- 3 files changed, 152 insertions(+), 95 deletions(-) diff --git a/src/jackpal/androidterm/EmulatorView.java b/src/jackpal/androidterm/EmulatorView.java index 26e7c3eea..92e1a1011 100644 --- a/src/jackpal/androidterm/EmulatorView.java +++ b/src/jackpal/androidterm/EmulatorView.java @@ -39,7 +39,6 @@ import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; -import android.view.ViewGroup.LayoutParams; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.CorrectionInfo; import android.view.inputmethod.EditorInfo; @@ -75,7 +74,6 @@ public class EmulatorView extends View implements GestureDetector.OnGestureListe private int mVisibleWidth; private int mVisibleHeight; - private Rect mVisibleRect = new Rect(); private TermSession mTermSession; @@ -163,8 +161,6 @@ public class EmulatorView extends View implements GestureDetector.OnGestureListe private static final int CURSOR_BLINK_PERIOD = 1000; - private static final int SCREEN_CHECK_PERIOD = 1000; - private boolean mCursorVisible = true; private boolean mIsSelectingText = false; @@ -186,22 +182,6 @@ public class EmulatorView extends View implements GestureDetector.OnGestureListe private boolean mIsActive = false; - /** - * True if we must poll to discover if the view has changed size. - * This is the only known way to detect the view changing size due to - * the IME being shown or hidden in API level <= 7. - */ - private boolean mbPollForWindowSizeChange = AndroidCompat.SDK <= 7; - - private Runnable mCheckSize = mbPollForWindowSizeChange - ? new Runnable() { - public void run() { - updateSize(false); - mHandler.postDelayed(this, SCREEN_CHECK_PERIOD); - } - } - : null; - private Runnable mBlinkCursor = new Runnable() { public void run() { if (mCursorBlink != 0) { @@ -215,21 +195,13 @@ public void run() { } }; - private boolean mRedoLayout = false; - private GestureDetector mGestureDetector; private GestureDetector.OnGestureListener mExtGestureListener; private float mScrollRemainder; private TermKeyListener mKeyListener; - private WindowSizeCallback mSizeCallback; private String mImeBuffer = ""; - // Used by activity to inform us how much of the window belongs to us - public interface WindowSizeCallback { - public abstract void onGetSize(Rect rect); - } - /** * Our message handler class. Implements a periodic callback. */ @@ -266,18 +238,12 @@ public void setDensity(DisplayMetrics metrics) { public void onResume() { mIsActive = true; updateSize(false); - if (mbPollForWindowSizeChange) { - mHandler.postDelayed(mCheckSize, SCREEN_CHECK_PERIOD); - } if (mCursorBlink != 0) { mHandler.postDelayed(mBlinkCursor, CURSOR_BLINK_PERIOD); } } public void onPause() { - if (mbPollForWindowSizeChange) { - mHandler.removeCallbacks(mCheckSize); - } if (mCursorBlink != 0) { mHandler.removeCallbacks(mBlinkCursor); } @@ -628,10 +594,6 @@ private void commonConstructor(Context context, TermSession session) { mKeyListener = new TermKeyListener(session); } - public void setWindowSizeCallback(WindowSizeCallback callback) { - mSizeCallback = callback; - } - public void setExtGestureListener(GestureDetector.OnGestureListener listener) { mExtGestureListener = listener; } @@ -988,53 +950,20 @@ private void updateSize(int w, int h) { public void updateSize(boolean force) { if (mKnownSize) { - getWindowVisibleDisplayFrame(mVisibleRect); - /* Work around bug in getWindowVisibleDisplayFrame, and avoid - distracting visual glitch otherwise */ - if (!mSettings.showStatusBar()) { - mVisibleRect.top = 0; - } - if (mSizeCallback != null) { - // Let activity adjust our size - mSizeCallback.onGetSize(mVisibleRect); - } - int w = mVisibleRect.width(); - int h = mVisibleRect.height(); + int w = getWidth(); + int h = getHeight(); // Log.w("Term", "(" + w + ", " + h + ")"); if (force || w != mVisibleWidth || h != mVisibleHeight) { mVisibleWidth = w; mVisibleHeight = h; - - LayoutParams params = getLayoutParams(); - params.width = w; - params.height = h; - setLayoutParams(params); - mRedoLayout = true; - updateSize(mVisibleWidth, mVisibleHeight); } } } - /** - * Called when the view changes size. - * (Note: Not always called on Android 1.5 or Android 1.6) - */ - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - if (mIsActive) { - updateSize(false); - } - } - @Override protected void onDraw(Canvas canvas) { updateSize(false); - if (mRedoLayout) { - requestLayout(); - mRedoLayout = false; - } if (mEmulator == null) { // Not ready yet diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index 3c7841b55..002085e3f 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -203,14 +203,6 @@ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float ve } } - private EmulatorView.WindowSizeCallback mSizeCallback = new EmulatorView.WindowSizeCallback() { - public void onGetSize(Rect rect) { - if (mActionBarMode == TermSettings.ACTION_BAR_MODE_ALWAYS_VISIBLE) { - // Fixed action bar takes space away from the EmulatorView - rect.top += mActionBar.getHeight(); - } - } - }; private View.OnKeyListener mBackKeyListener = new View.OnKeyListener() { public boolean onKey(View v, int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK && mActionBarMode == TermSettings.ACTION_BAR_MODE_HIDES && mActionBar.isShowing()) { @@ -349,15 +341,7 @@ private EmulatorView createEmulatorView(TermSession session) { getWindowManager().getDefaultDisplay().getMetrics(metrics); EmulatorView emulatorView = new EmulatorView(this, session, metrics); - FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( - FrameLayout.LayoutParams.WRAP_CONTENT, - FrameLayout.LayoutParams.WRAP_CONTENT, - Gravity.LEFT - ); - emulatorView.setLayoutParams(params); - emulatorView.setExtGestureListener(new EmulatorViewGestureListener(emulatorView)); - emulatorView.setWindowSizeCallback(mSizeCallback); emulatorView.setOnKeyListener(mBackKeyListener); registerForContextMenu(emulatorView); @@ -435,16 +419,15 @@ public void onResume() { if (onResumeSelectWindow >= 0) { mViewFlipper.setDisplayedChild(onResumeSelectWindow); onResumeSelectWindow = -1; - } else { - mViewFlipper.resumeCurrentView(); } + mViewFlipper.onResume(); } @Override public void onPause() { super.onPause(); - mViewFlipper.pauseCurrentView(); + mViewFlipper.onPause(); if (mTermSessions != null) { mTermSessions.removeCallback(this); if (mWinListAdapter != null) { @@ -486,7 +469,7 @@ public void onConfigurationChanged(Configuration newConfig) { EmulatorView v = (EmulatorView) mViewFlipper.getCurrentView(); if (v != null) { - v.updateSize(true); + v.updateSize(false); } if (mWinListAdapter != null) { diff --git a/src/jackpal/androidterm/TermViewFlipper.java b/src/jackpal/androidterm/TermViewFlipper.java index f9f3bbf4e..586c5f7ab 100644 --- a/src/jackpal/androidterm/TermViewFlipper.java +++ b/src/jackpal/androidterm/TermViewFlipper.java @@ -20,12 +20,16 @@ import java.util.LinkedList; import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.os.Handler; import android.util.AttributeSet; import android.view.Gravity; import android.view.View; import android.widget.Toast; import android.widget.ViewFlipper; +import jackpal.androidterm.compat.AndroidCompat; import jackpal.androidterm.model.UpdateCallback; import jackpal.androidterm.util.TermSettings; @@ -33,6 +37,29 @@ public class TermViewFlipper extends ViewFlipper implements Iterable { private Context context; private Toast mToast; private LinkedList callbacks; + private boolean mStatusBarVisible = false; + + private int mCurWidth; + private int mCurHeight; + private Rect mVisibleRect = new Rect(); + private Rect mWindowRect = new Rect(); + private LayoutParams mChildParams = null; + private boolean mRedoLayout = false; + + /** + * True if we must poll to discover if the view has changed size. + * This is the only known way to detect the view changing size due to + * the IME being shown or hidden in API level <= 7. + */ + private boolean mbPollForWindowSizeChange = false; + private static final int SCREEN_CHECK_PERIOD = 1000; + private final Handler mHandler = new Handler(); + private Runnable mCheckSize = new Runnable() { + public void run() { + adjustChildSize(); + mHandler.postDelayed(this, SCREEN_CHECK_PERIOD); + } + }; class ViewFlipperIterator implements Iterator { int pos = 0; @@ -52,19 +79,32 @@ public void remove() { public TermViewFlipper(Context context) { super(context); - this.context = context; - callbacks = new LinkedList(); + commonConstructor(context); } public TermViewFlipper(Context context, AttributeSet attrs) { super(context, attrs); + commonConstructor(context); + } + + private void commonConstructor(Context context) { this.context = context; callbacks = new LinkedList(); + + updateVisibleRect(); + Rect visible = mVisibleRect; + mChildParams = new LayoutParams(visible.width(), visible.height(), + Gravity.TOP|Gravity.LEFT); } public void updatePrefs(TermSettings settings) { + mStatusBarVisible = settings.showStatusBar(); int[] colorScheme = settings.getColorScheme(); setBackgroundColor(colorScheme[3]); + + if (AndroidCompat.SDK < 8) { + mbPollForWindowSizeChange = !mStatusBarVisible; + } } public Iterator iterator() { @@ -85,6 +125,20 @@ private void notifyChange() { } } + public void onPause() { + if (mbPollForWindowSizeChange) { + mHandler.removeCallbacks(mCheckSize); + } + pauseCurrentView(); + } + + public void onResume() { + if (mbPollForWindowSizeChange) { + mCheckSize.run(); + } + resumeCurrentView(); + } + public void pauseCurrentView() { EmulatorView view = (EmulatorView) getCurrentView(); if (view == null) { @@ -141,4 +195,95 @@ public void setDisplayedChild(int position) { resumeCurrentView(); notifyChange(); } + + @Override + public void addView(View v, int index) { + super.addView(v, index, mChildParams); + } + + @Override + public void addView(View v) { + super.addView(v, mChildParams); + } + + private void updateVisibleRect() { + Rect visible = mVisibleRect; + Rect window = mWindowRect; + + /* Get rectangle representing visible area of this view, as seen by + the activity (takes other views in the layout into account, but + not space used by the IME) */ + getGlobalVisibleRect(visible); + + /* Get rectangle representing visible area of this window (takes + IME into account, but not other views in the layout) */ + getWindowVisibleDisplayFrame(window); + /* Work around bug in getWindowVisibleDisplayFrame on API < 10, and + avoid a distracting height change as status bar hides otherwise */ + if (!mStatusBarVisible) { + window.top = 0; + } + + // Clip visible rectangle's top to the visible portion of the window + if (visible.width() == 0 && visible.height() == 0) { + visible.left = window.left; + visible.top = window.top; + } else { + if (visible.left < window.left) { + visible.left = window.left; + } + if (visible.top < window.top) { + visible.top = window.top; + } + } + // Always set the bottom of the rectangle to the window bottom + /* XXX This breaks with a split action bar, but if we don't do this, + it's possible that the view won't resize correctly on IME hide */ + visible.right = window.right; + visible.bottom = window.bottom; + } + + private void adjustChildSize() { + updateVisibleRect(); + Rect visible = mVisibleRect; + int width = visible.width(); + int height = visible.height(); + + if (mCurWidth != width || mCurHeight != height) { + mCurWidth = width; + mCurHeight = height; + + LayoutParams params = mChildParams; + params.width = width; + params.height = height; + for (View v : this) { + updateViewLayout(v, params); + } + mRedoLayout = true; + + EmulatorView currentView = (EmulatorView) getCurrentView(); + if (currentView != null) { + currentView.updateSize(false); + } + } + } + + /** + * Called when the view changes size. + * (Note: Not always called on Android < 2.2) + */ + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + adjustChildSize(); + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + @Override + protected void onDraw(Canvas canvas) { + if (mRedoLayout) { + requestLayout(); + mRedoLayout = false; + } + super.onDraw(canvas); + } } From 41a06bfb1891256896fa3ca71b292e0015c91b6d Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Sun, 25 Mar 2012 22:30:44 -0700 Subject: [PATCH 243/847] Check to see whether selection start/end are in IME buffer bounds Should fix a frequently-reported crash on Market: java.lang.StringIndexOutOfBoundsException: length=0; regionStart=0; regionLength=1 at java.lang.String.startEndAndLength(String.java:593) at java.lang.String.substring(String.java:1474) at jackpal.androidterm.EmulatorView$4.getSelectedText(EmulatorView.java:594) at com.android.internal.view.IInputConnectionWrapper.executeMessage(IInputConnectionWrapper.java:234) at com.android.internal.view.IInputConnectionWrapper$MyHandler.handleMessage(IInputConnectionWrapper.java:77) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loop(Looper.java:137) at android.app.ActivityThread.main(ActivityThread.java:4507) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:511) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:790) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:557) at dalvik.system.NativeStart.main(Native Method) Signed-off-by: Jack Palevich --- src/jackpal/androidterm/EmulatorView.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/jackpal/androidterm/EmulatorView.java b/src/jackpal/androidterm/EmulatorView.java index 92e1a1011..696c35205 100644 --- a/src/jackpal/androidterm/EmulatorView.java +++ b/src/jackpal/androidterm/EmulatorView.java @@ -459,6 +459,11 @@ public boolean commitText(CharSequence text, int newCursorPosition) { } private void clearComposingText() { + int len = mImeBuffer.length(); + if (mComposingTextStart > len || mComposingTextEnd > len) { + mComposingTextEnd = mComposingTextStart = 0; + return; + } setImeBuffer(mImeBuffer.substring(0, mComposingTextStart) + mImeBuffer.substring(mComposingTextEnd)); if (mCursor < mComposingTextStart) { @@ -517,6 +522,10 @@ public boolean setComposingText(CharSequence text, int newCursorPosition) { if (TermDebug.LOG_IME) { Log.w(TAG, "setComposingText(\"" + text + "\", " + newCursorPosition + ")"); } + int len = mImeBuffer.length(); + if (mComposingTextStart > len || mComposingTextEnd > len) { + return false; + } setImeBuffer(mImeBuffer.substring(0, mComposingTextStart) + text + mImeBuffer.substring(mComposingTextEnd)); mComposingTextEnd = mComposingTextStart + text.length(); @@ -557,6 +566,10 @@ public CharSequence getSelectedText(int flags) { if (TermDebug.LOG_IME) { Log.w(TAG, "getSelectedText " + flags); } + int len = mImeBuffer.length(); + if (mSelectedTextEnd >= len || mSelectedTextStart > mSelectedTextEnd) { + return ""; + } return mImeBuffer.substring(mSelectedTextStart, mSelectedTextEnd+1); } From 491e20043c385ba05e499bd0035554a6103a1f88 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Sun, 25 Mar 2012 22:30:51 -0700 Subject: [PATCH 244/847] Don't attempt fast resize on finished TranscriptScreen Should fix a frequently-reported crash on Market: java.lang.NullPointerException at jackpal.androidterm.session.TranscriptScreen.fastResize(TranscriptScreen.java:395) at jackpal.androidterm.session.TerminalEmulator.updateSize(TerminalEmulator.java:370) at jackpal.androidterm.session.TermSession.updateSize(TermSession.java:287) at jackpal.androidterm.EmulatorView.updateSize(EmulatorView.java:980) at jackpal.androidterm.EmulatorView.updateSize(EmulatorView.java:1014) at jackpal.androidterm.EmulatorView.onDraw(EmulatorView.java:1033) at android.view.View.draw(View.java:6903) at android.view.ViewGroup.drawChild(ViewGroup.java:1640) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1367) at android.view.View.draw(View.java:6906) at android.widget.FrameLayout.draw(FrameLayout.java:352) at android.view.ViewGroup.drawChild(ViewGroup.java:1640) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1367) at android.view.ViewGroup.drawChild(ViewGroup.java:1638) at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1367) at android.view.View.draw(View.java:6906) at android.widget.FrameLayout.draw(FrameLayout.java:352) at com.android.internal.policy.impl.PhoneWindow$DecorView.draw(PhoneWindow.java:1868) at android.view.ViewRoot.draw(ViewRoot.java:1407) at android.view.ViewRoot.performTraversals(ViewRoot.java:1163) at android.view.ViewRoot.handleMessage(ViewRoot.java:1727) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loop(Looper.java:123) at android.app.ActivityThread.main(ActivityThread.java:4627) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:521) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:876) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:634) at dalvik.system.NativeStart.main(Native Method) Signed-off-by: Jack Palevich --- src/jackpal/androidterm/session/TranscriptScreen.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/jackpal/androidterm/session/TranscriptScreen.java b/src/jackpal/androidterm/session/TranscriptScreen.java index bc4b96bef..91754213b 100644 --- a/src/jackpal/androidterm/session/TranscriptScreen.java +++ b/src/jackpal/androidterm/session/TranscriptScreen.java @@ -400,6 +400,10 @@ private String internalGetTranscriptText(StringBuilder colors, int selX1, int se } public boolean fastResize(int columns, int rows, int[] cursor) { + if (mData == null) { + // XXX Trying to resize a finished TranscriptScreen? + return true; + } if (mData.resize(columns, rows, cursor)) { mColumns = columns; mScreenRows = rows; From 4c3cedd22849c8b27a87e01a51370c51c9b36d59 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sun, 8 Apr 2012 20:44:46 -0700 Subject: [PATCH 245/847] Show the status bar by default. This helps on devices that place the menu key in the status bar. On those devices the user is stuck because they can't get to the menu key without showing the status bar, and they can't change the status bar preference without using the menu key. (Users on those devices can paint themselves into a corner by hiding the status bar manually, but hopefully they'll figure out that uninstalling and re-installing ATE will reset the preferences.) We originally hid the status bar by default way back in Android 1.0, when the standard screen size was 320 x 200. Screens are larger now, which makes it more reasonable to show the status bar by default. This change won't affect anyone who has already run ATE once, since the preferences will have already been set to the default value on the first run of the program. --- res/values/defaults.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/values/defaults.xml b/res/values/defaults.xml index 38383c1dd..303e322b6 100644 --- a/res/values/defaults.xml +++ b/res/values/defaults.xml @@ -1,7 +1,7 @@ - 0 + 1 1 0 0 From f59f1d931fd1a1accdcc308185db199476157af7 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 14 Apr 2012 12:04:12 -0700 Subject: [PATCH 246/847] Update Basque translation. Courtesy Asier Iturralde asier.iturralde@gmail.com --- res/values-eu/arrays.xml | 40 ++++++++++++++++++++++++++++++++++++++- res/values-eu/strings.xml | 35 ++++++++++++++++++++++++++++++++-- 2 files changed, 72 insertions(+), 3 deletions(-) diff --git a/res/values-eu/arrays.xml b/res/values-eu/arrays.xml index 1de7f78af..b071f015d 100644 --- a/res/values-eu/arrays.xml +++ b/res/values-eu/arrays.xml @@ -17,7 +17,7 @@ - Bistaratu egoera-barra + Erakutsi egoera-barra Ezkutatu egoera-barra @@ -27,6 +27,19 @@ 0 + + + Beti erakutsi ekintza-barra + Ezkutatu ekintza-barra (erakusteko ukitu pantailaren goialdea edo Menu tekla) + + + + + + 1 + 2 + + Kurtsore ez-keinukaria Kurtsore keinukaria @@ -97,6 +110,23 @@ 5 + + Terminal leiho guztiak ixten ditu + Soilik terminal leiho hau ixten du + Jarduera ixten du, saioak martxan utziz + ESC bidaltzen du terminalera + TAB bidaltzen du terminalera + + + + + 0 + 1 + 2 + 3 + 4 + + Jog bola \@ tekla @@ -177,4 +207,12 @@ Kamera Bat ere ez + + + + vt100 + screen + linux + + diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml index 7144e146f..b9f058681 100644 --- a/res/values-eu/strings.xml +++ b/res/values-eu/strings.xml @@ -27,6 +27,8 @@ Tekla bereziak Txandakatu soft teklatua + -Terminal leiho honen egoera berrezarri da. + Gaitu WakeLock Desgaitu WakeLock Gaitu WifiLock @@ -36,16 +38,26 @@ Hautatu testua Kopiatu guztia Itsatsi + Bidali kontrol tekla + Bidali fn tekla + + %1$d leihoa Terminal saioa martxan dago + Terminal saioa amaituta + Pantaila Egoera-barra - Bistaratu/ezkutatu egoera-barra. + Erakutsi/ezkutatu egoera-barra. Egoera-barra + Ekintza-barra + Aukeratu ekintza-barraren portaera (Android 3.0tik aurrera). + Ekintza-barraren portaera + Kurtsore-estiloa Hautatu kurtsore-estiloa. Kurtsore-estiloa @@ -56,6 +68,9 @@ Testua + Lehenetsi UTF-8 + UTF-8 modu lehenetsian gaitzen den ala ez. + Letra-tamaina Hautatu karaktereen tamaina puntutan. Letra-tamaina @@ -66,6 +81,10 @@ Teklatua + Atzera botoiaren portaera + Aukeratu atzera botoia sakatzeak zer egiten duen. + Atzera botoiaren portaera + Kontrol tekla Hautatu kontrol tekla. Kontrol tekla @@ -87,6 +106,13 @@ Bidali shell-era abiarazten denean. Hasierako Komandoa + Terminal mota + Terminal motaren berri eman shell-ari. + Terminal mota + + Itxi leihoa irtetean + Bere shell-a irtetean leiho bat itxi behar litzatekeen ala ez. + Kontrol and Funtzio Teklak @@ -96,5 +122,10 @@ actual function key key name. --> FNKEY 1..9 : F1-F9\nFNKEY 0 : F10\nFNKEY W : Gora\nFNKEY A : Ezkerra\nFNKEY S : Behera\nFNKEY D : Eskuina\nFNKEY P : Hurrengo-orria\nFNKEY N : Aurreko-orria\nFNKEY T : Tabulatzailea\nFNKEY L : | (kanalizazioa)\nFNKEY U : _ (azpimarra)\nFNKEY E : Kontrol-[ (ESC)\nFNKEY X : Ezabatu\nFNKEY I : Txertatu\nFNKEY H : Etxea\nFNKEY F : Bukaera\nFNKEY . : Kontrol-\\\n Ez da funtzio teklarik ezarri. - + + Leiho hau itxi? + + Exekutatu hautazko komandoak Terminal Emulatzailean + Aplikazioari baimena ematen dio Android Terminal Emulatzailean leiho berriak irekitzeko eta leiho hauetan komandoak exekutatzeko Android Terminal Emulatzailearen baimen guztiekin, Interneterako eta zure SD txartelerako sarbidea barne. + From 52dfbe677ae014da57f785a7c59b6f1cdc641ac1 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 14 Apr 2012 12:10:18 -0700 Subject: [PATCH 247/847] Move uses clauses in front of activity clause. Suggested by Android SDK Lint. --- AndroidManifest.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 80518944d..69cc6016c 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -2,6 +2,8 @@ package="jackpal.androidterm" android:versionName="1.0.41" android:versionCode="42" android:installLocation="auto"> + + @@ -43,6 +45,4 @@ android:label="@string/window_list" /> - - From 33abe793abfbc2c49701bf90f8f612b5bfe029c4 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 14 Apr 2012 12:22:39 -0700 Subject: [PATCH 248/847] Use FloatMath.ceil instead of Math.ceil Apparently slightly faster. This is a Lint suggestion. --- src/jackpal/androidterm/EmulatorView.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/jackpal/androidterm/EmulatorView.java b/src/jackpal/androidterm/EmulatorView.java index 696c35205..2c9af2342 100644 --- a/src/jackpal/androidterm/EmulatorView.java +++ b/src/jackpal/androidterm/EmulatorView.java @@ -33,6 +33,7 @@ import android.os.Handler; import android.text.ClipboardManager; import android.util.DisplayMetrics; +import android.util.FloatMath; import android.util.Log; import android.view.GestureDetector; import android.view.KeyCharacterMap; @@ -1189,8 +1190,8 @@ public PaintRenderer(int fontSize, mTextPaint.setAntiAlias(true); mTextPaint.setTextSize(fontSize); - mCharHeight = (int) Math.ceil(mTextPaint.getFontSpacing()); - mCharAscent = (int) Math.ceil(mTextPaint.ascent()); + mCharHeight = (int) FloatMath.ceil(mTextPaint.getFontSpacing()); + mCharAscent = (int) FloatMath.ceil(mTextPaint.ascent()); mCharDescent = mCharHeight + mCharAscent; mCharWidth = mTextPaint.measureText(EXAMPLE_CHAR, 0, 1); } From 66a51e7897844841d66b93bc4217c309a583a0f2 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 14 Apr 2012 12:35:25 -0700 Subject: [PATCH 249/847] Move atari_small.png to the right directories. It's drawable for API level 3 devices, and drawable-nodpi for more recent API levels. --- res/drawable-mdpi/atari_small.png | Bin 1006 -> 0 bytes .../atari_small.png | Bin res/drawable/atari_small.png | Bin 0 -> 974 bytes 3 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 res/drawable-mdpi/atari_small.png rename res/{drawable-hdpi => drawable-nodpi}/atari_small.png (100%) create mode 100644 res/drawable/atari_small.png diff --git a/res/drawable-mdpi/atari_small.png b/res/drawable-mdpi/atari_small.png deleted file mode 100644 index 535e295c8a6192d713267fe6882e789a738b00c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1006 zcmVh>Ouo}ap3hl zm|vrldsk|!*FQXKK;wtn$BkjpVCS+|00VY1`UB$pE2Id&hYh?vIle#+WFE*!fC20W zuW)|9S0XB+X9k1aIS>0h!Tj@eF#xzHs0m0aXd0Z=3VtEI20+Wb_yCE5>c)`4n!vEv zyk>YgQ2gw<7yyLw7_dsccTw*vm;;AUXC9=Zz|HRwIBhJy-}r zCn47O1Dt*BWOgw~;`U6|j|j6-)`oIzkvx&pZ8aNF>W{jPElPU%t+gOW z%8XjO$ncl{+^A!_J+q7e5LG#OWv_@L<@U8>c_u&d+*4G&2eflPgFKcNVTToIUF5Ia9x9TuWugbzE(iwXRbkY4MEOXJwBYjMv_}oa#p`r z1+UNX{f9BVVaheXI%eEC&GC$EA47!QV?NmPME`PhT;y>P2~U@jb&K70$PZ;nIQ_NQ zMdPrI6S)5T`b14(+@}Ii1)T?uYISx&tp-yI?u7zq&nO-SbymR!)j0BF>A2)KKcA7c z(r3pj)r+N`3V%mLT09IoFRW0QDSA1|JG432KUAEZy0eYZKVj_R2Qw`iINGS&_o*2z zLN4@xR`mQ&M^|bN7cVH{4uJe?$B6I%f+vhE70gPLTbl0b09*t>#OAoWD^{ZUMZ6@K z9~Y#J&`atWE~9Mm7|RcZa1mS!Ob1*Gt>sH$>2 zq(NDL@wDtufp~^0fidS=Ku^8LohU{#M`sj;k3tYu!op*ag9sgW-O=ujNwrca6^kjV zMB0MZ{+?1>HvYA^}K^w;sSuKNwH8g)8#X&2^K%28bb6c$N>nP zo6ftO<}fvF3U$N1Nd9#`v36PadxU2+f25#ewEEq;A>|Id1vV;J`!e=jDf&dB7D+p; ztNZ~(h%J?wdO)%3yQ{XS^rkM;7jXHOUXW1MUM3bXxb}1mLEG6AF4a1)A?|f6u1EhH zxjbGDr(rE}N>}kV(%M<{sZi94raX7v0bfckNsuFNE!CB?LJfNnMFH5%Dqv zn{GvAMTSXXu`alfXZO6t38FUV!S1O9Ko}K)D_uQNVbUA4qxO=|7L0(GA$79dn?4a0 z=-tLx>_k$c7b=%6g_t1Hr5|0vw(ca_g&2J^?)dr+DEfXS){B(8-dZU3OSaSj&TDCZ wtYy%*)B({;qTMyu;KzM83cGl= Date: Sat, 14 Apr 2012 12:38:39 -0700 Subject: [PATCH 250/847] Remove unused imports. --- src/jackpal/androidterm/Term.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index 002085e3f..ff146b05c 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -28,7 +28,6 @@ import android.content.ServiceConnection; import android.content.res.Configuration; import android.content.res.Resources; -import android.graphics.Rect; import android.net.Uri; import android.net.wifi.WifiManager; import android.os.Bundle; @@ -52,7 +51,6 @@ import android.view.Window; import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; -import android.widget.FrameLayout; import android.widget.TextView; import android.widget.Toast; From c3d85685a8426a9a3a3b6cf212b2616c0ee358fc Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 14 Apr 2012 13:32:39 -0700 Subject: [PATCH 251/847] Fix 4x8 fonts on API level 4+ (Use a runtime API level check to choose whether to load the nodpi version.) --- .../{atari_small.png => atari_small_nodpi.png} | Bin src/jackpal/androidterm/EmulatorView.java | 5 +++-- 2 files changed, 3 insertions(+), 2 deletions(-) rename res/drawable-nodpi/{atari_small.png => atari_small_nodpi.png} (100%) diff --git a/res/drawable-nodpi/atari_small.png b/res/drawable-nodpi/atari_small_nodpi.png similarity index 100% rename from res/drawable-nodpi/atari_small.png rename to res/drawable-nodpi/atari_small_nodpi.png diff --git a/src/jackpal/androidterm/EmulatorView.java b/src/jackpal/androidterm/EmulatorView.java index 2c9af2342..522e5c6d2 100644 --- a/src/jackpal/androidterm/EmulatorView.java +++ b/src/jackpal/androidterm/EmulatorView.java @@ -1110,8 +1110,9 @@ public Bitmap4x8FontRenderer(Resources resources, int forePaintIndex, int forePaintColor, int backPaintIndex, int backPaintColor) { super(forePaintIndex, forePaintColor, backPaintIndex, backPaintColor); - mFont = BitmapFactory.decodeResource(resources, - R.drawable.atari_small); + int fontResource = AndroidCompat.SDK <= 3 ? R.drawable.atari_small + : R.drawable.atari_small_nodpi; + mFont = BitmapFactory.decodeResource(resources,fontResource); mPaint = new Paint(); mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); } From 977ff82064798a97eeb9de286b9faf637c3ee031 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 14 Apr 2012 14:15:35 -0700 Subject: [PATCH 252/847] Update version number to 1.0.42 --- AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 69cc6016c..bb67507b8 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,6 +1,6 @@ From 71a6787c899f6fa68c599490f2c6a1070b063b70 Mon Sep 17 00:00:00 2001 From: gLes Date: Tue, 17 Apr 2012 02:09:55 +0200 Subject: [PATCH 253/847] Added Hungarian translation --- res/values-hu/arrays.xml | 217 ++++++++++++++++++++++++++++++++++++++ res/values-hu/strings.xml | 130 +++++++++++++++++++++++ 2 files changed, 347 insertions(+) create mode 100644 res/values-hu/arrays.xml create mode 100644 res/values-hu/strings.xml diff --git a/res/values-hu/arrays.xml b/res/values-hu/arrays.xml new file mode 100644 index 000000000..312343167 --- /dev/null +++ b/res/values-hu/arrays.xml @@ -0,0 +1,217 @@ + + + + + + Megjelenítés + Elrejtés + + + + + 1 + 0 + + + + + Mindig jelenjen meg + Elrejtés (a megjelenítéshez érintsd meg a képernyő tetejét vagy a menü gombot) + + + + + + 1 + 2 + + + + Statikus kurzor + Villogó kurzor + + + + + 0 + 1 + + + + Téglalap + Aláhúzás + Függőleges + + + + + 0 + 1 + 2 + + + + 4 x 8 képpont + 6 pt + 7 pt + 8 pt + 9 pt + 10 pt + 12 pt + 14 pt + 16 pt + 20 pt + + + + + 0 + 6 + 7 + 8 + 9 + 10 + 12 + 14 + 16 + 20 + + + + Fekete szöveg, fehér háttér + Fehér szöveg, fekete háttér + Fehér szöveg, kék háttér + Zöld szöveg, fekete háttér + Sárga szöveg, fekete háttér + Piros szöveg, fekete háttér + + + + + 0 + 1 + 2 + 3 + 4 + 5 + + + + Bezárja az összes terminál ablakot + Bezárja az aktív terminál ablakot + Kilép a programból, de a munkamenet tovább fut a háttérben + ESC-et küld a terminálra + TAB-ot küld a terminálra + + + + + 0 + 1 + 2 + 3 + 4 + + + + Görgő + \@ billentyű + Bal Alt billentyű + Jobb Alt billentyű + Hangerő fel gomb + Hangerő le gomb + Kamera gomb + Nincs + + + + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + + + + Görgő + \@ billentyű + Bal Alt billentyű + Jobb Alt billentyű + Hangerő fel gomb + Hangerő le gomb + Kamera gomb + Nincs + + + + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + + + + Karakterenkénti bevitel + Szavankénti bevitel + + + + + 0 + 1 + + + + + Görgő + \@ + Bal-Alt + Jobb-Alt + Hang-fel + Hang-le + Kamera + Nincs + + + + + Görgő + \@ + Bal-Alt + Jobb-Alt + Hang-fel + Hang-le + Kamera + Nincs + + + + + vt100 + screen + linux + + diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml new file mode 100644 index 000000000..49b508ffe --- /dev/null +++ b/res/values-hu/strings.xml @@ -0,0 +1,130 @@ + + + + Terminál Emulátor + Beállítások + Új ablak + Ablak bezárása + Ablakok + Előző ablak + Következő ablak + Alaphelyzet + Küldés emailben + Speciális billentyűk + Billentyűzet ki/bekapcsolása + + Az terminál ablak alaphelyzetbe állítva. + + Alvás megakadályozása + Alvás engedélyezése + WiFi-alvás megakadályozása + WiFi-alvás engedélyezése + + Szöveg szerkesztése + Szöveg kijelölése + Az összes másolása + Beillesztés + Control billentyű küldése + Fn billentyű küldése + + Ablak %1$d + + Aktív munkamenet + + A munkamenet befejeződött + + + Képernyő + + Értesítési sáv + Értesítési sáv megjelenítése/elrejtése. + Értesítési sáv + + Akciósáv + Válaszd ki az akciósáv viselkedési módját (csak Android 3.0 és afölött). + Akciósáv viselkedése + + Kurzor stílusa + Válaszd ki a kurzor stílusát. + Kurzor stílusa + + Kurzor villogás + Állítsd be a kurzor villogását. + Kurzor villogás + + Szöveg + + UTF-8 alapértelmezett + UTF-8 mód engedélyezése alapértelmezésként. + + Betűméret + Karakterek magasságának megadása pontban. + Betűméret + + Színek + Válaszd ki a szöveg és a háttér színét. + Színek + + Billentyűzet + + Vissza gomb viselkedése + Válaszd ki, mi történjen a Vissza gomb megnyomásakor. + Vissza gomb viselkedése + + Control billentyű + Válaszd ki a Control billentyűleütést kiváltó gombot. + Control billentyű + + Funkció billentyű + Válaszd ki a funkció billentyűket kiváltó gombot. + Funkció billentyű + + Beviteli mód + Beviteli mód kiválasztása a szoftveres billentyűzethez. + Beviteli mód + + Konzol + Parancssor + Add meg a konzolt elindító parancsot. + Konzol + + Indító parancs + Induláskor lefuttatja a konzol. + Indító parancs + + Terminál típusa + Milyen terminál típust jelentsen a konzolnak. + Terminál típusa + + Ablak bezárása kilépéskor + Terminál ablak bezárása, ha a konzol is bezáródik. + + Control és funkció billentyűk + + CTRLKEY Szóköz : Control-@ (NUL)\nCTRLKEY A..Z : Control-A..Z\nCTRLKEY 5 : Control-]\nCTRLKEY 6 : Control-^\nCTRLKEY 7 : Control-_\nCTRLKEY 9 : F11\nCTRLKEY 0 : F12 + Nincs beállítva Control billentyű. + + FNKEY 1..9 : F1-F9\nFNKEY 0 : F10\nFNKEY W : Fel\nFNKEY A : Balra\nFNKEY S : Le\nFNKEY D : Jobbra\nFNKEY P : PageUp\nFNKEY N : PageDown\nFNKEY T : Tab\nFNKEY L : | (pipe)\nFNKEY U : _ (aláhúzás)\nFNKEY E : Control-[ (ESC)\nFNKEY X : Delete\nFNKEY I : Insert\nFNKEY H : Home\nFNKEY F : End\nFNKEY . : Control-\\\n + Nincs beállítva funkció billentyű. + + Bezárja ezt az ablakot? + + Tetszőleges parancsok futtatása a Terminál Emulátorban + Lehetővé teszi az alkalmazások számára új ablakok megnyitását az Android Terminál Emulátorban és a megnyitott ablakokban tetszőleges parancsokat futtathatnak az Android Terminál Emulátor összes jogosultságával, beleértve a teljes hozzáférést az Internethez és az SD-kártyához. + From b7cc6d551aac48acdee617a52909b91eb9a6241a Mon Sep 17 00:00:00 2001 From: EauLand Date: Sat, 21 Apr 2012 22:02:01 +0300 Subject: [PATCH 254/847] Update res/values-fr/strings.xml --- res/values-fr/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml index 391d36222..c37fbf95d 100644 --- a/res/values-fr/strings.xml +++ b/res/values-fr/strings.xml @@ -20,11 +20,11 @@ Paramètres Nouvelle fenêtre Fermer fenêtre - Fenêtre + Fenêtres Fenêtre Préc. Fenêtre Suiv. Terminal par défaut - Envoyer par e-mail + Envoyer un e-mail Touches spéciales Afficher/Masquer Clavier From 8d567a430ff73e1827e55b8aa6019a944d9ebca8 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Mon, 9 Apr 2012 20:53:26 -0700 Subject: [PATCH 255/847] Behave sanely when selected shell is the empty string or doesn't exist Instead of crashing or exiting immediately when something is wrong with the user's chosen shell, fall back to the default shell, which gives the user a chance to go into the preferences and fix the error. --- .../androidterm/session/TermSession.java | 18 ++++++++++++++++-- src/jackpal/androidterm/util/TermSettings.java | 8 +++++++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/jackpal/androidterm/session/TermSession.java b/src/jackpal/androidterm/session/TermSession.java index d6cb632a6..d6c8c3d6d 100644 --- a/src/jackpal/androidterm/session/TermSession.java +++ b/src/jackpal/androidterm/session/TermSession.java @@ -16,8 +16,10 @@ package jackpal.androidterm.session; +import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; @@ -203,8 +205,20 @@ public void write(int codePoint) { private void createSubprocess(int[] processId) { String shell = mSettings.getShell(); ArrayList argList = parse(shell); - String arg0 = argList.get(0); - String[] args = argList.toArray(new String[1]); + + String arg0; + String[] args; + try { + arg0 = argList.get(0); + if (!(new File(arg0)).exists()) { + throw new FileNotFoundException(arg0); + } + args = argList.toArray(new String[1]); + } catch (Exception e) { + argList = parse(mSettings.getFailsafeShell()); + arg0 = argList.get(0); + args = argList.toArray(new String[1]); + } String termType = mSettings.getTermType(); String[] env = new String[1]; diff --git a/src/jackpal/androidterm/util/TermSettings.java b/src/jackpal/androidterm/util/TermSettings.java index a7f34bfb4..260a220d8 100644 --- a/src/jackpal/androidterm/util/TermSettings.java +++ b/src/jackpal/androidterm/util/TermSettings.java @@ -40,6 +40,7 @@ public class TermSettings { private int mFnKeyId; private int mUseCookedIME; private String mShell; + private String mFailsafeShell; private String mInitialCommand; private String mTermType; private boolean mCloseOnExit; @@ -125,7 +126,8 @@ private void readDefaultPrefs(Resources res) { mControlKeyId = Integer.parseInt(res.getString(R.string.pref_controlkey_default)); mFnKeyId = Integer.parseInt(res.getString(R.string.pref_fnkey_default)); mUseCookedIME = Integer.parseInt(res.getString(R.string.pref_ime_default)); - mShell = res.getString(R.string.pref_shell_default); + mFailsafeShell = res.getString(R.string.pref_shell_default); + mShell = mFailsafeShell; mInitialCommand = res.getString(R.string.pref_initialcommand_default); mTermType = res.getString(R.string.pref_termtype_default); mCloseOnExit = res.getBoolean(R.bool.pref_close_window_on_process_exit_default); @@ -241,6 +243,10 @@ public String getShell() { return mShell; } + public String getFailsafeShell() { + return mFailsafeShell; + } + public String getInitialCommand() { return mInitialCommand; } From b9b1e03913e8169c8eaf12d6ed9bf12b7dfce7ca Mon Sep 17 00:00:00 2001 From: damor Date: Sat, 28 Apr 2012 19:23:46 +0200 Subject: [PATCH 256/847] Added READ_LOGS permission(Issue #99). Fixes Issue #99 --- AndroidManifest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index bb67507b8..0d31cbaeb 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -7,6 +7,7 @@ + Date: Sat, 28 Apr 2012 13:22:07 -0700 Subject: [PATCH 257/847] Add example showing how to use intents to open ATE --- examples/intents/AndroidManifest.xml | 17 ++++ examples/intents/ant.properties | 17 ++++ examples/intents/build.xml | 83 +++++++++++++++++++ examples/intents/proguard-project.txt | 20 +++++ examples/intents/project.properties | 14 ++++ examples/intents/res/layout/main.xml | 36 ++++++++ examples/intents/res/values/strings.xml | 11 +++ .../sample/intents/IntentSampleActivity.java | 45 ++++++++++ 8 files changed, 243 insertions(+) create mode 100644 examples/intents/AndroidManifest.xml create mode 100644 examples/intents/ant.properties create mode 100644 examples/intents/build.xml create mode 100644 examples/intents/proguard-project.txt create mode 100644 examples/intents/project.properties create mode 100644 examples/intents/res/layout/main.xml create mode 100644 examples/intents/res/values/strings.xml create mode 100644 examples/intents/src/jackpal/androidterm/sample/intents/IntentSampleActivity.java diff --git a/examples/intents/AndroidManifest.xml b/examples/intents/AndroidManifest.xml new file mode 100644 index 000000000..82fc4d8e0 --- /dev/null +++ b/examples/intents/AndroidManifest.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + diff --git a/examples/intents/ant.properties b/examples/intents/ant.properties new file mode 100644 index 000000000..b0971e891 --- /dev/null +++ b/examples/intents/ant.properties @@ -0,0 +1,17 @@ +# This file is used to override default values used by the Ant build system. +# +# This file must be checked into Version Control Systems, as it is +# integral to the build system of your project. + +# This file is only used by the Ant script. + +# You can use this to override default values such as +# 'source.dir' for the location of your java source folder and +# 'out.dir' for the location of your output folder. + +# You can also use it define how the release builds are signed by declaring +# the following properties: +# 'key.store' for the location of your keystore and +# 'key.alias' for the name of the key to use. +# The password will be asked during the build when you use the 'release' target. + diff --git a/examples/intents/build.xml b/examples/intents/build.xml new file mode 100644 index 000000000..96dcfd96f --- /dev/null +++ b/examples/intents/build.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/intents/proguard-project.txt b/examples/intents/proguard-project.txt new file mode 100644 index 000000000..f2fe1559a --- /dev/null +++ b/examples/intents/proguard-project.txt @@ -0,0 +1,20 @@ +# To enable ProGuard in your project, edit project.properties +# to define the proguard.config property as described in that file. +# +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in ${sdk.dir}/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the ProGuard +# include property in project.properties. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/examples/intents/project.properties b/examples/intents/project.properties new file mode 100644 index 000000000..0840b4a05 --- /dev/null +++ b/examples/intents/project.properties @@ -0,0 +1,14 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system edit +# "ant.properties", and override values to adapt the script to your +# project structure. +# +# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): +#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt + +# Project target. +target=android-15 diff --git a/examples/intents/res/layout/main.xml b/examples/intents/res/layout/main.xml new file mode 100644 index 000000000..f941df0a6 --- /dev/null +++ b/examples/intents/res/layout/main.xml @@ -0,0 +1,36 @@ + + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index c01a2e7fa..a023a3702 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -163,4 +163,5 @@ Ctrl-Tab: cycle window, Ctrl-Shift-N: new window, Ctrl-Shift-V: paste. Keyboard shortcuts disabled. + View FAQ diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index 41d787c54..ceb158d3e 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -140,6 +140,7 @@ public void onReceive(Context context, Intent intent) { } }; // Available on API 12 and later + private static final String FAQ_URL = "http://github.com/jackpal/Android-Terminal-Emulator/wiki/Frequently-Asked-Questions"; private static final int FLAG_INCLUDE_STOPPED_PACKAGES = 0x20; private TermService mTermService; @@ -689,7 +690,8 @@ public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); MenuItemCompat.setShowAsAction(menu.findItem(R.id.menu_new_window), MenuItemCompat.SHOW_AS_ACTION_ALWAYS); MenuItemCompat.setShowAsAction(menu.findItem(R.id.menu_close_window), MenuItemCompat.SHOW_AS_ACTION_IF_ROOM); - return true; + MenuItemCompat.setShowAsAction(menu.findItem(R.id.action_view_faq), MenuItemCompat.SHOW_AS_ACTION_IF_ROOM); + return true; } @Override @@ -699,8 +701,11 @@ public boolean onOptionsItemSelected(MenuItem item) { doPreferences(); } else if (id == R.id.menu_new_window) { doCreateNewWindow(); - } else if (id == R.id.menu_close_window) { + } else if (id == R.id.menu_close_window) { confirmCloseWindow(); + } else if (id == R.id.action_view_faq) { + Intent openFaq = new Intent(Intent.ACTION_VIEW, Uri.parse(FAQ_URL)); + startActivity(openFaq); } else if (id == R.id.menu_window_list) { startActivityForResult(new Intent(this, WindowList.class), REQUEST_CHOOSE_WINDOW); } else if (id == R.id.menu_reset) { From 8c1df19d143800199ba58398da615f24ee81e693 Mon Sep 17 00:00:00 2001 From: godlessfather Date: Wed, 5 Mar 2014 10:37:39 -0600 Subject: [PATCH 628/847] Fixed indention --- src/jackpal/androidterm/Term.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index ceb158d3e..d94d9a5ed 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -701,9 +701,9 @@ public boolean onOptionsItemSelected(MenuItem item) { doPreferences(); } else if (id == R.id.menu_new_window) { doCreateNewWindow(); - } else if (id == R.id.menu_close_window) { + } else if (id == R.id.menu_close_window) { confirmCloseWindow(); - } else if (id == R.id.action_view_faq) { + } else if (id == R.id.action_view_faq) { Intent openFaq = new Intent(Intent.ACTION_VIEW, Uri.parse(FAQ_URL)); startActivity(openFaq); } else if (id == R.id.menu_window_list) { From 60af9625fdf1a529f86553dbb98795a31022e48f Mon Sep 17 00:00:00 2001 From: godlessfather Date: Wed, 5 Mar 2014 10:42:08 -0600 Subject: [PATCH 629/847] Adding link to FAQ in Action Bar --- res/drawable-hdpi/ic_action_help.png | Bin 0 -> 1162 bytes res/drawable-ldpi/ic_action_help.png | Bin 0 -> 699 bytes res/drawable-mdpi/ic_action_help.png | Bin 0 -> 855 bytes res/drawable-xhdpi/ic_action_help.png | Bin 0 -> 1345 bytes res/drawable/ic_action_help.png | Bin 0 -> 948 bytes 5 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 res/drawable-hdpi/ic_action_help.png create mode 100644 res/drawable-ldpi/ic_action_help.png create mode 100644 res/drawable-mdpi/ic_action_help.png create mode 100644 res/drawable-xhdpi/ic_action_help.png create mode 100644 res/drawable/ic_action_help.png diff --git a/res/drawable-hdpi/ic_action_help.png b/res/drawable-hdpi/ic_action_help.png new file mode 100644 index 0000000000000000000000000000000000000000..2dc838ed55e7574e645cd1b4b7475fba579ae727 GIT binary patch literal 1162 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY0wn)GsXhawSkfJR9T^xl_H+M9WCijWi-X*q z7}lMWc?skwBzpw;GB8xBF)%c=FfjZA3N^f7U???UV0e|lz+g3lfkC`r&aOZkpafHr zx4R3&|Mvbf`++>p0*}aI1_o|n5N2eUHAjMhfw|e!#W5t~-rE_m88V?F$LovLeqSt^ z9I<-hf(rpt#kewAS+$S$uhJFU$XKhv(d6Q^R3a$4_QLdE3L0xerR)!xiMlRa^h05b z!qTlvL|PUZW{HYUUY30;DR+_L&63T#i?4|r&(!_)_?-K8{?GN+<>zewyt6HTH^*2& zJ8VtF#&x-o|CW87uz-)-v61Oe<*k?QD;La3yM2|VYU0r)GyFW8_qLer>=lz*dAX_Q zH7DEL_iOeW83*U=iE`MzV`|Liy-iy`DRg^GcDd88$~EWW-(A-wr(D{6k@;)l#P|tu z#~*)UxE_0`u;cDh+n2?EYXnRutb0_sb3W_z(z)068tbS3tGOj{!mO~4=XGwj?dNp| zOcS@N&gyax7w3~;x+?jrI#KJ-!9v-G@;bAgl`F+SXx_c(*<$}3eUSC%6Hxv--a4-}}FoZ96r&_~O%jSDw%5=ka(L@YA#} z;MI@$%VQnNdi*OoX5W!K?J<+Fv(xy)wchq6B_D$?b{adJGro{irQy@awfyCtC5Lw! zewz2e)9{a&#K-cO#`Aw%uHH54bldGb{pz0RRd!nYGcV@syI=i6>6Podq>FRby2~t9 zmi@%{^?^=`Y_HzA33AI~oELbW&YSKYTkK{(@888O9GtqTdpYhXN1yy-9`G>X>EE7* zoLsr>_a3KZOb(AUvR-6AWxMGUt6t^SEzX|jbe@*JD}BBC@vh3p=4u(zK-a*`%D~{`Kco358glbf ZGSez?YiL;9r3}=-;OXk;vd$@?2>>GN3Y`D| literal 0 HcmV?d00001 diff --git a/res/drawable-ldpi/ic_action_help.png b/res/drawable-ldpi/ic_action_help.png new file mode 100644 index 0000000000000000000000000000000000000000..543308b1d23798accc4e0584cc991fd9d9be713e GIT binary patch literal 699 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k0wldT1B8JTOS+@4BLl<6e(pbstUx|vage(c z!@6@aFM%9|WRD45bDP46hOx7_4S6Fo+k-*%fF5lweBo zc6VX;-`;;_Kaj^+;1OBOz`zZ(f?Jr8Vb&Z8pwU}AT^vI^j=!C3>k%C&(Ki40-Hw(v z1xC^KOAEJK-8gvA_ux@qkHVED0xez{tmWd|N;^Dcw*-7l7gULGIeJtq>V*PtisoFE z?k4-#R}0KEUi$7Ux!|byeV#OL(}Z&C=X-3P*Ye+QT=mX(+Vz9(8{SCxKjjoGsye}U zTtxV3W7>~&+pKAJFApkuaj1H?xyDX=lVG`k^+l2#J9j|W4TdK_7QDC6pR4txu<@|* zInU3bua_oti{yTo%~-n^G@9R+tqzhsPw zUHfzPZruz%sT3aeA4@!^Kb*8~f_+MthSX%vszzSfiNWh{nyGXx__`;Z;o{jp2JNi= z%b&K-Tq?0m{i*ID{(47&SjD8|5zMDJ@yrEVJN)S$m3aZK|E!Z*mKhlGexi5 zmw54=J@BZ*vaH)T4(JDOTqU>cA9JEb)rI$OdnN!QRkg%5q9i4;B-JXpC>2OC7#SFv z=o%R78kmL{SXddFTbUT>8kkub7<~L^G#^DnZhlH;S|x4`4U4;!ff^V*UHx3vIVCg! E0Q~+Cf&c&j literal 0 HcmV?d00001 diff --git a/res/drawable-mdpi/ic_action_help.png b/res/drawable-mdpi/ic_action_help.png new file mode 100644 index 0000000000000000000000000000000000000000..0cfed93adc297d0a94478078aad010a2b67fdc01 GIT binary patch literal 855 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}a}tmUKs7M+SzC{oH>NS%G}U;vjb? zhIQv;UIIA^$sR$z3=CCj3=9n|3=F@3LJcn%7)pVryh>nTu$sZZAYL$MSD+10f+@+{ z-G$+Qd;gjJKptm-M`SSr1Gg{;GcwGYBLP$q;OXKR5^?zLw1eIvg(Ao8Kc^j!O)OVt zRedo<^oi;UKG$|7D;IIYMaj!ud%c(qS1e%KSnsgtR!fJ16DxnEaMv!;5c7iz78zU@ z)tbHbYS~^Cu{-CZ-1f@v%#1=o4k8~;7KK}X4JceMcj2+sDJBL! z&Fc9Vgl8xS-8$qd`>f4weSFQ~R0Z3UEYDvYWt3X__x9mj@2d=PS>no1PS1U(@K%r4 z?b~MQ2HA5~b|IG+1@2TnowoEucaNQ}?5qy9_UIv?wZZUpq)D(CFmq+(8ehAY) zc3;%_Z`ibZoh6std1hR@H=Qa5_K4w03D-Ia<}=4lB4_PT-6wH;hV_r7BKD6YkB2;Yz@Pd~ z!F`&NQ(KqJ4CXcowgbs|Yh%Mz4#%3XcuumHtMZ}IB*|8zMrhoH)@fCkxx595-RTD75sg}4#l%yncptHiCLVR4r-Py>Uf LtDnm{r-UW|i`QU0 literal 0 HcmV?d00001 diff --git a/res/drawable-xhdpi/ic_action_help.png b/res/drawable-xhdpi/ic_action_help.png new file mode 100644 index 0000000000000000000000000000000000000000..6cf0862102637133072c151e0deeeef54237107f GIT binary patch literal 1345 zcmZuxe>9T|82&bCny^p1=2nb_=GLgj4R<&)#yUBy&Mjf&)D?F#LPi`L;X~@)n`Dhm zO5ILBT))*tD?diGNgc^5guZnmX1KE{cRJ_ZbN{&S`#$e`&hwo2J?~#nL2wYw1icvz z0Dwtg0F|MiQEM<-ukY{5Qr+}pee`}B63Pg=s5 z0R9l-lUyMp3dlFkeLOumdHpHO92X$>2>QIPA&r<{Vp6)!Fp=H`YeMn>KucseI<*1@|Z66rZGN5r>0J$M)ZFBK>o;~LY8j#>u^y)NuR0Y@StGOd9hxiZJiHzu(af5xcX(L#g?G8v%av}nbd4}kGH(M96`Uem&?2WeQ9Fr zPKZ8YenDF!C}q3MTs0Xvwa6rSsG1&a$2j^dgkQhJe(6;cA+3@_8d@e0Jy8@qAXe6ovTzr z$RxQYmTS%|>6`i#&t`u{Ul~=u{JHUH)=rBbdQr?tp&0RsbvlNKrTeJ6P=xd$=OVMQ zYse9W;wD&I1oz|ej=5y+e(%~&w7J+j4e)#^F>#GY*@gK`xClx3wa1OXl&aII3$Z&b zbbNS?1fuX}Zqmd-irU~IHlb^8F!(H1w9I!#OKxknoA%YoRQ+ZdApkHviHK1w8Ge;) zs`7N6Tk2j-d&pNLxSF#cN4wt!L`I68Wx49EZ!pIhyr2H5ueLBC!RfZw;q01>b-qY*?$APD01uo literal 0 HcmV?d00001 diff --git a/res/drawable/ic_action_help.png b/res/drawable/ic_action_help.png new file mode 100644 index 0000000000000000000000000000000000000000..10b993162e3e1df4a5a9b321035aee9752679012 GIT binary patch literal 948 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}a}tmUKs7M+SzC{oH>NS%G}U;vjb? zhIQv;UIIA^$sR$z3=CCj3=9n|3=F@3LJcn%7)pVryh>nTu$sZZAYL$MSD+10f+@+{ z-G$+Qd;gjJKptm-M`SSr1Gg{;GcwGYBLP&g*3-o?B;xSfY2F?oi4tw|f1bayg?p0W zQ`N&7=2KS}D(13?E3yc=2sm!xxTvsOSc4^YTcC%fm4-_|cad^Lx7gaAD3Qj<8?2W? zjzvFQVfpIJPd{${=EmY@XDpc6_O1T>>Hq!j|K5N9JDpkj#(}7Z9%c($i?>u?tO<0B-Nlr<X`IWgdXr;*M_$9bo`+ZNSN5cDd(vHIMq;3rH0PKO`;ZsN$~lHl=JU1GRx z(aebt+;^Uuv#Wz`!Ky8_tMm)GQYYGJ^*_0Bs8%`T|M|F;&SyV1_^X{QpBAm^`NAP# zQnZfQI;&;<759Sp8rqdsE2)UAU`{Ojx-ru$>2q-5t4%^1ilz%x`ef#G-~Ksw8uRPW zo!l!;JR^P?zgf`By3}vO{Uz_0oX;vd#l-mS!P>yyW#2b!$)7jx>>P)N8n)F}WQ(77 zl(@}5UvyJR>1F9&A;*{(Q@0cgFf^xMbUVJ|YwC2T$;}xqe^;r_+ZVlV$MM;dU2f_H zGVr&>Z+#T+r*FHFJF8;KMjy@$ms303TRr0o?wOh#{JNyjjJ4^6TDoz&&n@lgTjVdB z-0awLwRyJ2if1c4+n0FO_C~#%DG9pQ0DrbJP7D9h8s%xg;w%+i>L+4);H2r2S5|HE)?%%DB7yB2W3vuEu7` zv`u?gd}WP(@5r9s#-29Zxv`TahhE~Rg5DgC+)j5G07(8A5 KT-G@yGywn-e~L%| literal 0 HcmV?d00001 From 561f6229d8ac148198818fcd567c769d42561026 Mon Sep 17 00:00:00 2001 From: godlessfather Date: Wed, 5 Mar 2014 10:48:37 -0600 Subject: [PATCH 630/847] Fixed color --- res/drawable-xhdpi/ic_action_help.png | Bin 1345 -> 653 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/res/drawable-xhdpi/ic_action_help.png b/res/drawable-xhdpi/ic_action_help.png index 6cf0862102637133072c151e0deeeef54237107f..740fbfc1eacf81fff8c1e6b8797faadeb3fb6549 100644 GIT binary patch delta 580 zcmX@e)yp~|q+USZWVe9PRt5$pDNh&2kczmsw|%p22Z%U4Tzz5Lk_mh!MiN_;{r9r& zPu-JxBl^OX1+1JbQk54Ry)uJKLo(vMCrGXQzWllLJ;w>}C(7L3%f?z?&CpZ>`Y4gLD9#Hzkl_j%Y1&I_e?lkb0*Km9Xm zO`PB>_R_s^@@tjarSd{5$|KF|M?y_5Tb$=UW@>)xOK86@RjU;k;DOvL9ytEb053lrNl<)*g6 z(vZCy{@hdYvsN0{qOUlw7?q-aRLsJCm5!mcKv$wu8< z%eq^qAAHDE@k_!$8*rxG8tOVqxO%?uZ(=|9qNIP$ccrCMF*QT9(I zKc4AB>+eyGW9%u@yzYd`9mxxENB0`c~K~lslpX1 z$pVa_1}5?H3D53K_-gTe~ HDWM4fmLLkw delta 1277 zcmZvce^8PK9L8TTkHS)3$Hc7^B{f{36}r@+9*J7)>eRZGsc2ozbRdzMXnKH#zUr*D z%p?@dqOI$P^9O2;jPeJX#%tD@Nu{Rt(xoY)4#9HP-FEw9&pr43<9Y6$`~LMQvA&Nz zyH{Wfl1*&O2LM>1-N~Tu<+CPraYtj0IAW%eOsPX|<;H;eC|pt~eMR`&oqbOS(+7&m zPO!*ED?GO`D-suqU%|oS4cfI$r#+$GVv~sK1KF;|vj;0;*(t*q5YZ$TM8%%F@us3e zW3#W*S;}snS0U?UMEt`ge~SN9umEYul%iLFM2dDKXK}w*@Wx zH#J6=DRqsLfsEOPQ9n&#=SaM6C8?#MxtWiF(^neQ>-!%Ta(F{&RJ>8;4Bq@YpJxTXykfx)d^>)8K@cwEK?7Q9lXatzsi=Ec zzw79b`x-XN$;qjbrfVb8j>RO{L@?*3*F;aEG)z^uWjSA&Ojd@s#!Wle*u7@*fJnRd(nYTi!u#QBcJgjY%jGDSE?o7l5H9NHYs(&FA z{%K%$8B=k$wY61Xka&s6E^;_H7pjK_CW^euy2tO|9OS+Hro~(B^9X}8_yG+w(Untd zUCXxRS2{3vd{1`pWl0y;L#8c0@r!~dWk=Rz@(if#San}t-zZd$5^k1rT&ky@_4kLF zEf&})*=L$@D1{L_*ViC}Z88<=C(LGZU|wF{y=ln>9~!D=>n`IefWL$-7HI2kTw;(S z$EY<)e0x@D_xSr14(9{*;*jy#Ps#&=O%C66VOV2QIqEt4L?Ri_qzjDg7*bZhcd^~@ zW%Qs<_ZwJSjPwxl5BlV6dE?tcb~)EI0SH5AiOI?XoT362Qj8}3(&F?TC+;duzqU_AhaVql7>HIiO)CHN}I i|J&uiAX!FA?j-ShLJ3y2W=;nzYiH Date: Wed, 5 Mar 2014 12:00:44 -0600 Subject: [PATCH 631/847] Fixed tabbing indentation --- src/jackpal/androidterm/Term.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index d94d9a5ed..52783e105 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -690,8 +690,8 @@ public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); MenuItemCompat.setShowAsAction(menu.findItem(R.id.menu_new_window), MenuItemCompat.SHOW_AS_ACTION_ALWAYS); MenuItemCompat.setShowAsAction(menu.findItem(R.id.menu_close_window), MenuItemCompat.SHOW_AS_ACTION_IF_ROOM); - MenuItemCompat.setShowAsAction(menu.findItem(R.id.action_view_faq), MenuItemCompat.SHOW_AS_ACTION_IF_ROOM); - return true; + MenuItemCompat.setShowAsAction(menu.findItem(R.id.action_view_faq), MenuItemCompat.SHOW_AS_ACTION_IF_ROOM); + return true; } @Override From b5602bda720a53331974fdd3c5fceedfa75da171 Mon Sep 17 00:00:00 2001 From: godlessfather Date: Wed, 5 Mar 2014 12:08:30 -0600 Subject: [PATCH 632/847] Move url to intent --- src/jackpal/androidterm/Term.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index 52783e105..abbea5932 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -140,7 +140,6 @@ public void onReceive(Context context, Intent intent) { } }; // Available on API 12 and later - private static final String FAQ_URL = "http://github.com/jackpal/Android-Terminal-Emulator/wiki/Frequently-Asked-Questions"; private static final int FLAG_INCLUDE_STOPPED_PACKAGES = 0x20; private TermService mTermService; @@ -704,7 +703,7 @@ public boolean onOptionsItemSelected(MenuItem item) { } else if (id == R.id.menu_close_window) { confirmCloseWindow(); } else if (id == R.id.action_view_faq) { - Intent openFaq = new Intent(Intent.ACTION_VIEW, Uri.parse(FAQ_URL)); + Intent openFaq = new Intent(Intent.ACTION_VIEW, Uri.parse("http://github.com/jackpal/Android-Terminal-Emulator/wiki/Frequently-Asked-Questions")); startActivity(openFaq); } else if (id == R.id.menu_window_list) { startActivityForResult(new Intent(this, WindowList.class), REQUEST_CHOOSE_WINDOW); From cbcd38577fbd2ae346c3700550903ce1bcf47271 Mon Sep 17 00:00:00 2001 From: godlessfather Date: Wed, 5 Mar 2014 12:27:55 -0600 Subject: [PATCH 633/847] Change order of FAQ in list --- res/menu/main.xml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/res/menu/main.xml b/res/menu/main.xml index 69c132aed..47613214f 100644 --- a/res/menu/main.xml +++ b/res/menu/main.xml @@ -22,6 +22,9 @@ + @@ -40,8 +43,4 @@ android:title="@string/enable_wakelock" /> -
From 85123b1f960e565c785cee10f0a9e1280f4987a4 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 8 Mar 2014 08:15:12 -0800 Subject: [PATCH 634/847] Rename FAQ to Help Use custom "help" page optimized for beginners and viewing on mobile. --- res/menu/main.xml | 4 ++-- res/values/strings.xml | 2 +- src/jackpal/androidterm/Term.java | 15 ++++++++------- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/res/menu/main.xml b/res/menu/main.xml index 47613214f..de0c3f2a5 100644 --- a/res/menu/main.xml +++ b/res/menu/main.xml @@ -22,9 +22,9 @@ - + android:title="@string/help" /> diff --git a/res/values/strings.xml b/res/values/strings.xml index a023a3702..40bb58b2d 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -163,5 +163,5 @@ Ctrl-Tab: cycle window, Ctrl-Shift-N: new window, Ctrl-Shift-V: paste. Keyboard shortcuts disabled. - View FAQ + Help diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index abbea5932..631542f1b 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -221,7 +221,7 @@ public EmulatorViewGestureListener(EmulatorView view) { public boolean onSingleTapUp(MotionEvent e) { // Let the EmulatorView handle taps if mouse tracking is active if (view.isMouseTrackingActive()) return false; - + //Check for link at tap location String link = view.getURLat(e.getX(), e.getY()); if(link != null) @@ -689,7 +689,7 @@ public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); MenuItemCompat.setShowAsAction(menu.findItem(R.id.menu_new_window), MenuItemCompat.SHOW_AS_ACTION_ALWAYS); MenuItemCompat.setShowAsAction(menu.findItem(R.id.menu_close_window), MenuItemCompat.SHOW_AS_ACTION_IF_ROOM); - MenuItemCompat.setShowAsAction(menu.findItem(R.id.action_view_faq), MenuItemCompat.SHOW_AS_ACTION_IF_ROOM); + MenuItemCompat.setShowAsAction(menu.findItem(R.id.action_help), MenuItemCompat.SHOW_AS_ACTION_IF_ROOM); return true; } @@ -702,9 +702,10 @@ public boolean onOptionsItemSelected(MenuItem item) { doCreateNewWindow(); } else if (id == R.id.menu_close_window) { confirmCloseWindow(); - } else if (id == R.id.action_view_faq) { - Intent openFaq = new Intent(Intent.ACTION_VIEW, Uri.parse("http://github.com/jackpal/Android-Terminal-Emulator/wiki/Frequently-Asked-Questions")); - startActivity(openFaq); + } else if (id == R.id.action_help) { + Intent openHelp = new Intent(Intent.ACTION_VIEW, + Uri.parse("http://jackpal.github.com/Android-Terminal-Emulator/help/index.html")); + startActivity(openHelp); } else if (id == R.id.menu_window_list) { startActivityForResult(new Intent(this, WindowList.class), REQUEST_CHOOSE_WINDOW); } else if (id == R.id.menu_reset) { @@ -1153,9 +1154,9 @@ private void doUIToggle(int x, int y, int width, int height) { } getCurrentEmulatorView().requestFocus(); } - + /** - * + * * Send a URL up to Android to be handled by a browser. * @param link The URL to be opened. */ From b44789b68d94df0faa9547a94649bc10c37d4de0 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 8 Mar 2014 13:08:11 -0800 Subject: [PATCH 635/847] Help URL is localizable. --- res/values/strings.xml | 1 + src/jackpal/androidterm/Term.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 40bb58b2d..614289418 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -164,4 +164,5 @@ Keyboard shortcuts disabled. Help + http://jackpal.github.com/Android-Terminal-Emulator/help/index.html diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index 631542f1b..847e80ce5 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -704,7 +704,7 @@ public boolean onOptionsItemSelected(MenuItem item) { confirmCloseWindow(); } else if (id == R.id.action_help) { Intent openHelp = new Intent(Intent.ACTION_VIEW, - Uri.parse("http://jackpal.github.com/Android-Terminal-Emulator/help/index.html")); + Uri.parse(getString(R.string.help_url))); startActivity(openHelp); } else if (id == R.id.menu_window_list) { startActivityForResult(new Intent(this, WindowList.class), REQUEST_CHOOSE_WINDOW); From 8bb107184bdd902b70f39209913d262523757efd Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 8 Mar 2014 13:41:16 -0800 Subject: [PATCH 636/847] v1.0.58 --- AndroidManifest.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 7b737f0af..f9a4a2b26 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,7 +1,7 @@ From 5f7d3cfd7db16f5855603b92e207b4094451448b Mon Sep 17 00:00:00 2001 From: pyler Date: Fri, 14 Mar 2014 18:03:31 +0100 Subject: [PATCH 637/847] SK translation for HELP --- res/values-sk/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml index e1a35e470..7dbce490c 100644 --- a/res/values-sk/strings.xml +++ b/res/values-sk/strings.xml @@ -158,4 +158,7 @@ Používať klávesové skratky Ctrl-Tab: prepnúť okno, Ctrl-Shift-N: nové okno, Ctrl-Shift-V: prilepiť. Klávesové skratky sú zakázané. + + Pomocník + http://jackpal.github.com/Android-Terminal-Emulator/help/index.html From eb29a2777b4af3446a06abb45272fa826db8e57a Mon Sep 17 00:00:00 2001 From: godlessfather Date: Sun, 16 Mar 2014 16:31:08 -0500 Subject: [PATCH 638/847] Moving help to bottom of menu --- res/drawable-hdpi/ic_action_help.png | Bin 1162 -> 0 bytes res/drawable-ldpi/ic_action_help.png | Bin 699 -> 0 bytes res/drawable-mdpi/ic_action_help.png | Bin 855 -> 0 bytes res/drawable-xhdpi/ic_action_help.png | Bin 653 -> 0 bytes res/drawable/ic_action_help.png | Bin 948 -> 0 bytes res/menu/main.xml | 1 - src/jackpal/androidterm/Term.java | 9 ++++----- 7 files changed, 4 insertions(+), 6 deletions(-) delete mode 100644 res/drawable-hdpi/ic_action_help.png delete mode 100644 res/drawable-ldpi/ic_action_help.png delete mode 100644 res/drawable-mdpi/ic_action_help.png delete mode 100644 res/drawable-xhdpi/ic_action_help.png delete mode 100644 res/drawable/ic_action_help.png diff --git a/res/drawable-hdpi/ic_action_help.png b/res/drawable-hdpi/ic_action_help.png deleted file mode 100644 index 2dc838ed55e7574e645cd1b4b7475fba579ae727..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1162 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY0wn)GsXhawSkfJR9T^xl_H+M9WCijWi-X*q z7}lMWc?skwBzpw;GB8xBF)%c=FfjZA3N^f7U???UV0e|lz+g3lfkC`r&aOZkpafHr zx4R3&|Mvbf`++>p0*}aI1_o|n5N2eUHAjMhfw|e!#W5t~-rE_m88V?F$LovLeqSt^ z9I<-hf(rpt#kewAS+$S$uhJFU$XKhv(d6Q^R3a$4_QLdE3L0xerR)!xiMlRa^h05b z!qTlvL|PUZW{HYUUY30;DR+_L&63T#i?4|r&(!_)_?-K8{?GN+<>zewyt6HTH^*2& zJ8VtF#&x-o|CW87uz-)-v61Oe<*k?QD;La3yM2|VYU0r)GyFW8_qLer>=lz*dAX_Q zH7DEL_iOeW83*U=iE`MzV`|Liy-iy`DRg^GcDd88$~EWW-(A-wr(D{6k@;)l#P|tu z#~*)UxE_0`u;cDh+n2?EYXnRutb0_sb3W_z(z)068tbS3tGOj{!mO~4=XGwj?dNp| zOcS@N&gyax7w3~;x+?jrI#KJ-!9v-G@;bAgl`F+SXx_c(*<$}3eUSC%6Hxv--a4-}}FoZ96r&_~O%jSDw%5=ka(L@YA#} z;MI@$%VQnNdi*OoX5W!K?J<+Fv(xy)wchq6B_D$?b{adJGro{irQy@awfyCtC5Lw! zewz2e)9{a&#K-cO#`Aw%uHH54bldGb{pz0RRd!nYGcV@syI=i6>6Podq>FRby2~t9 zmi@%{^?^=`Y_HzA33AI~oELbW&YSKYTkK{(@888O9GtqTdpYhXN1yy-9`G>X>EE7* zoLsr>_a3KZOb(AUvR-6AWxMGUt6t^SEzX|jbe@*JD}BBC@vh3p=4u(zK-a*`%D~{`Kco358glbf ZGSez?YiL;9r3}=-;OXk;vd$@?2>>GN3Y`D| diff --git a/res/drawable-ldpi/ic_action_help.png b/res/drawable-ldpi/ic_action_help.png deleted file mode 100644 index 543308b1d23798accc4e0584cc991fd9d9be713e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 699 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k0wldT1B8JTOS+@4BLl<6e(pbstUx|vage(c z!@6@aFM%9|WRD45bDP46hOx7_4S6Fo+k-*%fF5lweBo zc6VX;-`;;_Kaj^+;1OBOz`zZ(f?Jr8Vb&Z8pwU}AT^vI^j=!C3>k%C&(Ki40-Hw(v z1xC^KOAEJK-8gvA_ux@qkHVED0xez{tmWd|N;^Dcw*-7l7gULGIeJtq>V*PtisoFE z?k4-#R}0KEUi$7Ux!|byeV#OL(}Z&C=X-3P*Ye+QT=mX(+Vz9(8{SCxKjjoGsye}U zTtxV3W7>~&+pKAJFApkuaj1H?xyDX=lVG`k^+l2#J9j|W4TdK_7QDC6pR4txu<@|* zInU3bua_oti{yTo%~-n^G@9R+tqzhsPw zUHfzPZruz%sT3aeA4@!^Kb*8~f_+MthSX%vszzSfiNWh{nyGXx__`;Z;o{jp2JNi= z%b&K-Tq?0m{i*ID{(47&SjD8|5zMDJ@yrEVJN)S$m3aZK|E!Z*mKhlGexi5 zmw54=J@BZ*vaH)T4(JDOTqU>cA9JEb)rI$OdnN!QRkg%5q9i4;B-JXpC>2OC7#SFv z=o%R78kmL{SXddFTbUT>8kkub7<~L^G#^DnZhlH;S|x4`4U4;!ff^V*UHx3vIVCg! E0Q~+Cf&c&j diff --git a/res/drawable-mdpi/ic_action_help.png b/res/drawable-mdpi/ic_action_help.png deleted file mode 100644 index 0cfed93adc297d0a94478078aad010a2b67fdc01..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 855 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}a}tmUKs7M+SzC{oH>NS%G}U;vjb? zhIQv;UIIA^$sR$z3=CCj3=9n|3=F@3LJcn%7)pVryh>nTu$sZZAYL$MSD+10f+@+{ z-G$+Qd;gjJKptm-M`SSr1Gg{;GcwGYBLP$q;OXKR5^?zLw1eIvg(Ao8Kc^j!O)OVt zRedo<^oi;UKG$|7D;IIYMaj!ud%c(qS1e%KSnsgtR!fJ16DxnEaMv!;5c7iz78zU@ z)tbHbYS~^Cu{-CZ-1f@v%#1=o4k8~;7KK}X4JceMcj2+sDJBL! z&Fc9Vgl8xS-8$qd`>f4weSFQ~R0Z3UEYDvYWt3X__x9mj@2d=PS>no1PS1U(@K%r4 z?b~MQ2HA5~b|IG+1@2TnowoEucaNQ}?5qy9_UIv?wZZUpq)D(CFmq+(8ehAY) zc3;%_Z`ibZoh6std1hR@H=Qa5_K4w03D-Ia<}=4lB4_PT-6wH;hV_r7BKD6YkB2;Yz@Pd~ z!F`&NQ(KqJ4CXcowgbs|Yh%Mz4#%3XcuumHtMZ}IB*|8zMrhoH)@fCkxx595-RTD75sg}4#l%yncptHiCLVR4r-Py>Uf LtDnm{r-UW|i`QU0 diff --git a/res/drawable-xhdpi/ic_action_help.png b/res/drawable-xhdpi/ic_action_help.png deleted file mode 100644 index 740fbfc1eacf81fff8c1e6b8797faadeb3fb6549..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 653 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGoY)RhkE)4%caKYZ?lYt_f1s;*b z3=G`DAk4@xYmNj^kiEpy*OmPqGpm5U$!-CotqcrIQl2i3Ar*0NZ~JE54iIs8xcb7f zB@_5ej3l-w`|oAlpSmaYM)ZX%3s^Z>q$)2sdSwQehGfKhPmo&qefe|gdyW&{Pn5a6 z$rWfe7~F^n<pw zW%%Zw+Bt1^k-%L89{=3ivnubT@jZ!;`aJ(n_D=2#CTH7st$Tm^XONVCef_6pG7+B- zt)3qLEKF?Il$+WLOGEZ<_;Y{BlvRZ?;}1tmG{E;zt4-(0&g(H2{=TaV3>Z| z_3PEULW;~jmrY(T>u#NX@F7#hF9|1=h=w_z-x%LIbW>kQ=fIuy8+86&De+c2*JP?w z`D$a)w{8~B2cK_H}$_xHnz@k%dFc({2l1fz+Q$e9SIU`#Lr=T%4x=NWmdKI;Vst0Q>M5RR910 diff --git a/res/drawable/ic_action_help.png b/res/drawable/ic_action_help.png deleted file mode 100644 index 10b993162e3e1df4a5a9b321035aee9752679012..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 948 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}a}tmUKs7M+SzC{oH>NS%G}U;vjb? zhIQv;UIIA^$sR$z3=CCj3=9n|3=F@3LJcn%7)pVryh>nTu$sZZAYL$MSD+10f+@+{ z-G$+Qd;gjJKptm-M`SSr1Gg{;GcwGYBLP&g*3-o?B;xSfY2F?oi4tw|f1bayg?p0W zQ`N&7=2KS}D(13?E3yc=2sm!xxTvsOSc4^YTcC%fm4-_|cad^Lx7gaAD3Qj<8?2W? zjzvFQVfpIJPd{${=EmY@XDpc6_O1T>>Hq!j|K5N9JDpkj#(}7Z9%c($i?>u?tO<0B-Nlr<X`IWgdXr;*M_$9bo`+ZNSN5cDd(vHIMq;3rH0PKO`;ZsN$~lHl=JU1GRx z(aebt+;^Uuv#Wz`!Ky8_tMm)GQYYGJ^*_0Bs8%`T|M|F;&SyV1_^X{QpBAm^`NAP# zQnZfQI;&;<759Sp8rqdsE2)UAU`{Ojx-ru$>2q-5t4%^1ilz%x`ef#G-~Ksw8uRPW zo!l!;JR^P?zgf`By3}vO{Uz_0oX;vd#l-mS!P>yyW#2b!$)7jx>>P)N8n)F}WQ(77 zl(@}5UvyJR>1F9&A;*{(Q@0cgFf^xMbUVJ|YwC2T$;}xqe^;r_+ZVlV$MM;dU2f_H zGVr&>Z+#T+r*FHFJF8;KMjy@$ms303TRr0o?wOh#{JNyjjJ4^6TDoz&&n@lgTjVdB z-0awLwRyJ2if1c4+n0FO_C~#%DG9pQ0DrbJP7D9h8s%xg;w%+i>L+4);H2r2S5|HE)?%%DB7yB2W3vuEu7` zv`u?gd}WP(@5r9s#-29Zxv`TahhE~Rg5DgC+)j5G07(8A5 KT-G@yGywn-e~L%| diff --git a/res/menu/main.xml b/res/menu/main.xml index de0c3f2a5..f598287c7 100644 --- a/res/menu/main.xml +++ b/res/menu/main.xml @@ -23,7 +23,6 @@ android:title="@string/close_window" android:icon="@drawable/ic_menu_close_clear_cancel" /> Date: Sun, 16 Mar 2014 17:56:24 -0500 Subject: [PATCH 639/847] Move help to bottom --- res/menu/main.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/res/menu/main.xml b/res/menu/main.xml index f598287c7..166245a9e 100644 --- a/res/menu/main.xml +++ b/res/menu/main.xml @@ -22,8 +22,6 @@ - @@ -42,4 +40,6 @@ android:title="@string/enable_wakelock" /> + From 985bacf04b7ff370a3f53f13fa2b9b8e18c1993a Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sun, 23 Mar 2014 13:32:35 -0700 Subject: [PATCH 640/847] Some UTF-8 text with full-width characters. --- tests/fullWidthText | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 tests/fullWidthText diff --git a/tests/fullWidthText b/tests/fullWidthText new file mode 100644 index 000000000..5b96cff64 --- /dev/null +++ b/tests/fullWidthText @@ -0,0 +1,3 @@ +Make sure UTF-8 is enabled. +Full-width text: [ABC] +Normal-width text: [ABC ] From 8d98850f6fd81fd100e82182d7617c9988fde3ca Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sun, 23 Mar 2014 13:48:03 -0700 Subject: [PATCH 641/847] fix The cursor can not enclose wide character (Manually merged from maoabc / Android-Terminal-Emulator) --- .../emulatorview/Bitmap4x8FontRenderer.java | 3 ++- .../androidterm/emulatorview/PaintRenderer.java | 5 +++-- .../androidterm/emulatorview/TextRenderer.java | 4 +++- .../emulatorview/TranscriptScreen.java | 16 ++++++++++------ 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/Bitmap4x8FontRenderer.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/Bitmap4x8FontRenderer.java index f6e06fb54..16d53f7df 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/Bitmap4x8FontRenderer.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/Bitmap4x8FontRenderer.java @@ -121,7 +121,8 @@ public void drawTextRun(Canvas canvas, float x, float y, } } - public void drawCursor(Canvas canvas, float x, float y, int lineOffset, int cursorMode) { + public void drawCursor(Canvas canvas, float x, float y, int lineOffset, + int width, int cursorMode) { int destX = (int) x + kCharacterWidth * lineOffset; int destY = (int) y; drawCursorImp(canvas, destX, destY, kCharacterWidth, kCharacterHeight, cursorMode); diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/PaintRenderer.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/PaintRenderer.java index 951ff68d6..0e97c7e37 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/PaintRenderer.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/PaintRenderer.java @@ -91,9 +91,10 @@ public void drawTextRun(Canvas canvas, float x, float y, int lineOffset, } } - public void drawCursor(Canvas canvas, float x, float y, int lineOffset, int cursorMode) { + public void drawCursor(Canvas canvas, float x, float y, int lineOffset, + int width, int cursorMode) { float left = x + lineOffset * mCharWidth; - drawCursorImp(canvas, left, y, mCharWidth, mCharHeight, cursorMode); + drawCursorImp(canvas, left, y, mCharWidth * width, mCharHeight, cursorMode); } public int getCharacterHeight() { diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TextRenderer.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TextRenderer.java index 6a3e2472b..5d54de2a1 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TextRenderer.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TextRenderer.java @@ -41,5 +41,7 @@ interface TextRenderer { void drawTextRun(Canvas canvas, float x, float y, int lineOffset, int runWidth, char[] text, int index, int count, boolean cursor, int textStyle); - void drawCursor(Canvas canvas, float x, float y, int lineOffset, int cursorMode); + // width is width in characters. (1 for normal 2 for wide.) + void drawCursor(Canvas canvas, float x, float y, int lineOffset, + int width, int cursorMode); } diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TranscriptScreen.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TranscriptScreen.java index 452d531a3..9959ec13b 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TranscriptScreen.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TranscriptScreen.java @@ -43,7 +43,7 @@ class TranscriptScreen implements Screen { private int mScreenRows; private UnicodeTranscript mData; - + /** * Create a transcript screen. * @@ -167,6 +167,7 @@ public final void drawText(int row, Canvas canvas, float x, float y, TextRenderer renderer, int cx, int selx1, int selx2, String imeText, int cursorMode) { char[] line; StyleRow color; + int cursorWidth = 1; try { line = mData.getLine(row); color = mData.getLineColor(row); @@ -190,7 +191,7 @@ public final void drawText(int row, Canvas canvas, float x, float y, blank, 0, 1, true, defaultStyle); } else if (cx != -1) { // We need to draw the cursor - renderer.drawCursor(canvas, x, y, cx, cursorMode); + renderer.drawCursor(canvas, x, y, cx, cursorWidth, cursorMode); } return; @@ -236,6 +237,9 @@ public final void drawText(int row, Canvas canvas, float x, float y, lastRunStartIndex = index; forceFlushRun = false; } + if (cx == column) { + cursorWidth = width; + } runWidth += width; column += width; index += incr; @@ -263,7 +267,7 @@ public final void drawText(int row, Canvas canvas, float x, float y, } if (cx >= 0) { - renderer.drawCursor(canvas, x, y, cx, cursorMode); + renderer.drawCursor(canvas, x, y, cx, cursorWidth, cursorMode); } } @@ -409,9 +413,9 @@ public boolean fastResize(int columns, int rows, int[] cursor) { public void resize(int columns, int rows, int style) { init(columns, mTotalRows, rows, style); } - + /** - * + * * Return the UnicodeTranscript line at this row index. * @param row The row index to be queried * @return The line of text at this row index @@ -431,7 +435,7 @@ public char[] getScriptLine(int row) return null; } } - + /** * Get the line wrap status of the row provided. * @param row The row to check for line-wrap status From 4fb5ff9411430f5620ea60ee40f5ba16273312b8 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 29 Mar 2014 15:20:05 -0700 Subject: [PATCH 642/847] Fix eclipse warnings. --- .../sample/telnet/TelnetSession.java | 1 - .../sample/telnet/TermActivity.java | 1 - .../emulatorview/EmulatorView.java | 21 -------------- .../emulatorview/TerminalEmulator.java | 19 +++---------- src/jackpal/androidterm/RemoteInterface.java | 1 - src/jackpal/androidterm/ShellTermSession.java | 1 - src/jackpal/androidterm/Term.java | 28 ++++++------------- src/jackpal/androidterm/TermView.java | 1 - src/jackpal/androidterm/WindowList.java | 4 ++- .../androidterm/util/TermSettings.java | 2 -- .../emulatorview/InputConnectionTest.java | 25 ++++++++--------- .../emulatorview/ModifierKeyTest.java | 8 +++--- 12 files changed, 31 insertions(+), 81 deletions(-) diff --git a/examples/widget/src/jackpal/androidterm/sample/telnet/TelnetSession.java b/examples/widget/src/jackpal/androidterm/sample/telnet/TelnetSession.java index 4e759ca81..4f7b6d304 100644 --- a/examples/widget/src/jackpal/androidterm/sample/telnet/TelnetSession.java +++ b/examples/widget/src/jackpal/androidterm/sample/telnet/TelnetSession.java @@ -85,7 +85,6 @@ public class TelnetSession extends TermSession /* Telnet command processor state */ private boolean mInTelnetCommand = false; private int mTelnetCommand = 0; - private int mTelnetCommandArg = 0; private boolean mMultipleParameters = false; private int mLastInputByteProcessed = 0; diff --git a/examples/widget/src/jackpal/androidterm/sample/telnet/TermActivity.java b/examples/widget/src/jackpal/androidterm/sample/telnet/TermActivity.java index 19af0e477..73fb52386 100644 --- a/examples/widget/src/jackpal/androidterm/sample/telnet/TermActivity.java +++ b/examples/widget/src/jackpal/androidterm/sample/telnet/TermActivity.java @@ -19,7 +19,6 @@ import android.widget.Button; import android.widget.EditText; import android.widget.TextView; -import android.widget.Toast; import jackpal.androidterm.emulatorview.EmulatorView; import jackpal.androidterm.emulatorview.TermSession; diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/EmulatorView.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/EmulatorView.java index 7c40c70c6..8c5129a17 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/EmulatorView.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/EmulatorView.java @@ -24,7 +24,6 @@ import java.util.Arrays; import java.util.Hashtable; -import android.app.AlertDialog; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; @@ -106,7 +105,6 @@ public class EmulatorView extends View implements GestureDetector.OnGestureListe */ private int mTextSize = 10; - private int mCursorStyle; private int mCursorBlink; /** @@ -160,8 +158,6 @@ public class EmulatorView extends View implements GestureDetector.OnGestureListe private boolean mIsControlKeySent = false; private boolean mIsFnKeySent = false; - private String mTermType; - private boolean mMouseTracking; private float mDensity; @@ -947,22 +943,6 @@ public void setTextSize(int fontSize) { updateText(); } - /** - * Sets style information about the cursor. - * - * @param style The style of the cursor. - * @param blink Whether the cursor should blink. - */ - public void setCursorStyle(int style, int blink) { - mCursorStyle = style; - if (blink != 0 && mCursorBlink == 0) { - mHandler.postDelayed(mBlinkCursor, CURSOR_BLINK_PERIOD); - } else if (blink == 0 && mCursorBlink != 0) { - mHandler.removeCallbacks(mBlinkCursor); - } - mCursorBlink = blink; - } - /** * Sets the IME mode ("cooked" or "raw"). * @@ -1555,7 +1535,6 @@ public void setFnKeyCode(int keyCode) { public void setTermType(String termType) { mKeyListener.setTermType(termType); - mTermType = termType; } /** diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TerminalEmulator.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TerminalEmulator.java index 20597387a..326602a7b 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TerminalEmulator.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TerminalEmulator.java @@ -22,6 +22,7 @@ import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.nio.charset.CodingErrorAction; +import java.util.Locale; import android.util.Log; @@ -246,13 +247,6 @@ public void setKeyListener(TermKeyListener l) { */ private boolean mInsertMode; - /** - * Automatic newline mode. Configures whether pressing return on the - * keyboard automatically generates a return as well. Not currently - * implemented. - */ - private boolean mAutomaticNewlineMode; - /** * An array of tab stops. mTabStop[i] is true if there is a tab stop set for * column i. @@ -1296,7 +1290,7 @@ private void doEscLeftSquareBracket(byte b) { case 6: // Cursor position report (CPR): // Answer is ESC [ y ; x R, where x,y is // the cursor location. - byte[] cpr = String.format("\033[%d;%dR", + byte[] cpr = String.format(Locale.US, "\033[%d;%dR", mCursorRow + 1, mCursorCol + 1).getBytes(); mSession.write(cpr, 0, cpr.length); break; @@ -1516,10 +1510,6 @@ private void doSetMode(boolean newValue) { mInsertMode = newValue; break; - case 20: - mAutomaticNewlineMode = newValue; - break; - default: unknownParameter(modeBit); break; @@ -1791,10 +1781,10 @@ private void emit(int c, int style) { if (autoWrap) { mAboutToAutoWrap = (mCursorCol == mColumns - 1); - + //Force line-wrap flag to trigger even for lines being typed if(mAboutToAutoWrap) - mScreen.setLineWrap(mCursorRow); + mScreen.setLineWrap(mCursorRow); } mCursorCol = Math.min(mCursorCol + width, mColumns - 1); @@ -1891,7 +1881,6 @@ public void reset() { mDecFlags |= K_SHOW_CURSOR_MASK; mSavedDecFlags = 0; mInsertMode = false; - mAutomaticNewlineMode = false; mTopMargin = 0; mBottomMargin = mRows; mAboutToAutoWrap = false; diff --git a/src/jackpal/androidterm/RemoteInterface.java b/src/jackpal/androidterm/RemoteInterface.java index d58ee0d4c..beeea5d99 100644 --- a/src/jackpal/androidterm/RemoteInterface.java +++ b/src/jackpal/androidterm/RemoteInterface.java @@ -34,7 +34,6 @@ import jackpal.androidterm.util.TermSettings; public class RemoteInterface extends Activity { - private static final String ACTION_OPEN_NEW_WINDOW = "jackpal.androidterm.OPEN_NEW_WINDOW"; private static final String ACTION_RUN_SCRIPT = "jackpal.androidterm.RUN_SCRIPT"; static final String PRIVACT_OPEN_NEW_WINDOW = "jackpal.androidterm.private.OPEN_NEW_WINDOW"; diff --git a/src/jackpal/androidterm/ShellTermSession.java b/src/jackpal/androidterm/ShellTermSession.java index bc325f447..c10a30cf3 100644 --- a/src/jackpal/androidterm/ShellTermSession.java +++ b/src/jackpal/androidterm/ShellTermSession.java @@ -56,7 +56,6 @@ public class ShellTermSession extends TermSession { public static final int PROCESS_EXIT_FINISHES_SESSION = 0; public static final int PROCESS_EXIT_DISPLAYS_MESSAGE = 1; - private int mProcessExitBehavior = PROCESS_EXIT_FINISHES_SESSION; private String mProcessExitMessage; diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java index ce4f6fdad..5f021effb 100644 --- a/src/jackpal/androidterm/Term.java +++ b/src/jackpal/androidterm/Term.java @@ -20,7 +20,6 @@ import jackpal.androidterm.compat.ActivityCompat; import jackpal.androidterm.compat.AndroidCompat; import jackpal.androidterm.compat.MenuItemCompat; -import jackpal.androidterm.emulatorview.ColorScheme; import jackpal.androidterm.emulatorview.EmulatorView; import jackpal.androidterm.emulatorview.TermSession; import jackpal.androidterm.emulatorview.UpdateCallback; @@ -30,7 +29,6 @@ import jackpal.androidterm.util.SessionList; import jackpal.androidterm.util.TermSettings; -import java.io.UnsupportedEncodingException; import java.text.Collator; import java.util.Arrays; import java.util.List; @@ -223,11 +221,11 @@ public boolean onSingleTapUp(MotionEvent e) { if (view.isMouseTrackingActive()) return false; //Check for link at tap location - String link = view.getURLat(e.getX(), e.getY()); + String link = view.getURLat(e.getX(), e.getY()); if(link != null) - execURL(link); + execURL(link); else - doUIToggle((int) e.getX(), (int) e.getY(), view.getVisibleWidth(), view.getVisibleHeight()); + doUIToggle((int) e.getX(), (int) e.getY(), view.getVisibleWidth(), view.getVisibleHeight()); return true; } @@ -532,7 +530,6 @@ private void updatePrefs() { DisplayMetrics metrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(metrics); - ColorScheme colorScheme = new ColorScheme(mSettings.getColorScheme()); mViewFlipper.updatePrefs(mSettings); @@ -1041,13 +1038,6 @@ private void doPaste() { ClipboardManagerCompat clip = ClipboardManagerCompatFactory .getManager(getApplicationContext()); CharSequence paste = clip.getText(); - byte[] utf8; - try { - utf8 = paste.toString().getBytes("UTF-8"); - } catch (UnsupportedEncodingException e) { - Log.e(TermDebug.LOG_TAG, "UTF-8 encoding not found."); - return; - } getCurrentTermSession().write(paste.toString()); } @@ -1161,11 +1151,11 @@ private void doUIToggle(int x, int y, int width, int height) { */ private void execURL(String link) { - Uri webLink = Uri.parse(link); - Intent openLink = new Intent(Intent.ACTION_VIEW, webLink); - PackageManager pm = getPackageManager(); - List handlers = pm.queryIntentActivities(openLink, 0); - if(handlers.size() > 0) - startActivity(openLink); + Uri webLink = Uri.parse(link); + Intent openLink = new Intent(Intent.ACTION_VIEW, webLink); + PackageManager pm = getPackageManager(); + List handlers = pm.queryIntentActivities(openLink, 0); + if(handlers.size() > 0) + startActivity(openLink); } } diff --git a/src/jackpal/androidterm/TermView.java b/src/jackpal/androidterm/TermView.java index 945362879..f01285ade 100644 --- a/src/jackpal/androidterm/TermView.java +++ b/src/jackpal/androidterm/TermView.java @@ -37,7 +37,6 @@ public void updatePrefs(TermSettings settings, ColorScheme scheme) { } setTextSize(settings.getFontSize()); - setCursorStyle(settings.getCursorStyle(), settings.getCursorBlink()); setUseCookedIME(settings.useCookedIME()); setColorScheme(scheme); setBackKeyCharacter(settings.getBackKeyCharacter()); diff --git a/src/jackpal/androidterm/WindowList.java b/src/jackpal/androidterm/WindowList.java index d9bd51035..16cf42b33 100644 --- a/src/jackpal/androidterm/WindowList.java +++ b/src/jackpal/androidterm/WindowList.java @@ -45,8 +45,10 @@ public class WindowList extends ListActivity { * pressed. This allows the window's entry to be pressed without the close * button being triggered. * Idea and code shamelessly borrowed from the Android browser's tabs list. + * + * Used by layout xml. */ - private static class CloseButton extends ImageView { + public static class CloseButton extends ImageView { public CloseButton(Context context) { super(context); } diff --git a/src/jackpal/androidterm/util/TermSettings.java b/src/jackpal/androidterm/util/TermSettings.java index 1e8238f59..3302ab74b 100644 --- a/src/jackpal/androidterm/util/TermSettings.java +++ b/src/jackpal/androidterm/util/TermSettings.java @@ -61,8 +61,6 @@ public class TermSettings { private static final String STATUSBAR_KEY = "statusbar"; private static final String ACTIONBAR_KEY = "actionbar"; private static final String ORIENTATION_KEY = "orientation"; - private static final String CURSORSTYLE_KEY = "cursorstyle"; - private static final String CURSORBLINK_KEY = "cursorblink"; private static final String FONTSIZE_KEY = "fontsize"; private static final String COLOR_KEY = "color"; private static final String UTF8_KEY = "utf8_by_default"; diff --git a/tests/emulatorview-test/src/jackpal/androidterm/emulatorview/InputConnectionTest.java b/tests/emulatorview-test/src/jackpal/androidterm/emulatorview/InputConnectionTest.java index 8c91066ea..3d2caa255 100644 --- a/tests/emulatorview-test/src/jackpal/androidterm/emulatorview/InputConnectionTest.java +++ b/tests/emulatorview-test/src/jackpal/androidterm/emulatorview/InputConnectionTest.java @@ -1,26 +1,23 @@ package jackpal.androidterm.emulatorview; import android.test.AndroidTestCase; -import android.test.mock.MockApplication; -import android.test.mock.MockContext; -import android.util.DisplayMetrics; -import android.view.inputmethod.InputConnection; +// import android.test.mock.MockApplication; +// import android.test.mock.MockContext; +// import android.util.DisplayMetrics; +// import android.view.inputmethod.InputConnection; public class InputConnectionTest extends AndroidTestCase { + public InputConnectionTest() { + super(); + } - - - public InputConnectionTest() { - super(); - } - - public void testBackSpace() { + public void testBackSpace() { // MockContext cn = new MockContext(); // MockApplication a = new MockApplication(); -// TermSession ts = new TermSession(); +// TermSession ts = new TermSession(); // DisplayMetrics m = new DisplayMetrics(); // EmulatorView e = new EmulatorView(cn, ts, m); -// InputConnection c = e.onCreateInputConnection(null); - } +// InputConnection c = e.onCreateInputConnection(null); + } } diff --git a/tests/emulatorview-test/src/jackpal/androidterm/emulatorview/ModifierKeyTest.java b/tests/emulatorview-test/src/jackpal/androidterm/emulatorview/ModifierKeyTest.java index 97b8eb42f..e8077774c 100644 --- a/tests/emulatorview-test/src/jackpal/androidterm/emulatorview/ModifierKeyTest.java +++ b/tests/emulatorview-test/src/jackpal/androidterm/emulatorview/ModifierKeyTest.java @@ -1,14 +1,14 @@ package jackpal.androidterm.emulatorview; import android.test.AndroidTestCase; -import android.view.KeyEvent; +// import android.view.KeyEvent; public class ModifierKeyTest extends AndroidTestCase { - - public void testCtrlKey() { + + public void testCtrlKey() { // ModifierKey mk = new ModifierKey(KeyEvent.KEYCODE_CTRL_LEFT); // assertFalse(mk.isActive()); // mk.handleModifierKey(KeyEvent.KEYCODE_CTRL_LEFT, true); // assertTrue(mk.isActive()); - } + } } From b4d469ff3b3fec77d0a8160ff9555614489f9087 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 29 Mar 2014 15:34:12 -0700 Subject: [PATCH 643/847] Rename cursorStyle to selectionStyle. --- .../emulatorview/Bitmap4x8FontRenderer.java | 4 +- .../emulatorview/PaintRenderer.java | 4 +- .../emulatorview/TextRenderer.java | 15 ++++++- .../emulatorview/TranscriptScreen.java | 42 +++++++++---------- 4 files changed, 39 insertions(+), 26 deletions(-) diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/Bitmap4x8FontRenderer.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/Bitmap4x8FontRenderer.java index 16d53f7df..c4659a9da 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/Bitmap4x8FontRenderer.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/Bitmap4x8FontRenderer.java @@ -61,7 +61,7 @@ public int getTopMargin() { public void drawTextRun(Canvas canvas, float x, float y, int lineOffset, int runWidth, char[] text, int index, int count, - boolean cursor, int textStyle) { + boolean selectionStyle, int textStyle) { int foreColor = TextStyle.decodeForeColor(textStyle); int backColor = TextStyle.decodeBackColor(textStyle); int effect = TextStyle.decodeEffect(textStyle); @@ -85,7 +85,7 @@ public void drawTextRun(Canvas canvas, float x, float y, backColor += 8; } - if (cursor) { + if (selectionStyle) { backColor = TextStyle.ciCursor; } diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/PaintRenderer.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/PaintRenderer.java index 0e97c7e37..bb7a86557 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/PaintRenderer.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/PaintRenderer.java @@ -38,7 +38,7 @@ public PaintRenderer(int fontSize, ColorScheme scheme) { public void drawTextRun(Canvas canvas, float x, float y, int lineOffset, int runWidth, char[] text, int index, int count, - boolean cursor, int textStyle) { + boolean selectionStyle, int textStyle) { int foreColor = TextStyle.decodeForeColor(textStyle); int backColor = TextStyle.decodeBackColor(textStyle); int effect = TextStyle.decodeEffect(textStyle); @@ -51,7 +51,7 @@ public void drawTextRun(Canvas canvas, float x, float y, int lineOffset, backColor = temp; } - if (cursor) { + if (selectionStyle) { backColor = TextStyle.ciCursor; } diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TextRenderer.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TextRenderer.java index 5d54de2a1..734dc7d8f 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TextRenderer.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TextRenderer.java @@ -38,9 +38,22 @@ interface TextRenderer { int getCharacterHeight(); /** @return pixels above top row of text to avoid looking cramped. */ int getTopMargin(); + /** + * Draw a run of text + * @param canvas The canvas to draw into. + * @param x The x coordinate. + * @param y The y coordinate. + * @param lineOffset + * @param runWidth + * @param text + * @param index + * @param count + * @param selectionStyle True to draw the text using the "selected" style (for clipboard copy) + * @param textStyle + */ void drawTextRun(Canvas canvas, float x, float y, int lineOffset, int runWidth, char[] text, - int index, int count, boolean cursor, int textStyle); + int index, int count, boolean selectionStyle, int textStyle); // width is width in characters. (1 for normal 2 for wide.) void drawCursor(Canvas canvas, float x, float y, int lineOffset, int width, int cursorMode); diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TranscriptScreen.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TranscriptScreen.java index 9959ec13b..2dd0f4857 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TranscriptScreen.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TranscriptScreen.java @@ -199,7 +199,7 @@ public final void drawText(int row, Canvas canvas, float x, float y, int columns = mColumns; int lastStyle = 0; - boolean lastCursorStyle = false; + boolean lastSelectionStyle = false; int runWidth = 0; int lastRunStart = -1; int lastRunStartIndex = -1; @@ -208,7 +208,7 @@ public final void drawText(int row, Canvas canvas, float x, float y, int index = 0; while (column < columns) { int style = color.get(column); - boolean cursorStyle = false; + boolean selectionStyle = false; int incr = 1; int width; if (Character.isHighSurrogate(line[index])) { @@ -218,20 +218,20 @@ public final void drawText(int row, Canvas canvas, float x, float y, width = UnicodeTranscript.charWidth(line[index]); } if (column >= selx1 && column <= selx2) { - // Set cursor background color: - cursorStyle = true; + // Draw selection: + selectionStyle = true; } if (style != lastStyle - || cursorStyle != lastCursorStyle + || selectionStyle != lastSelectionStyle || (width > 0 && forceFlushRun)) { if (lastRunStart >= 0) { renderer.drawTextRun(canvas, x, y, lastRunStart, runWidth, line, lastRunStartIndex, index - lastRunStartIndex, - lastCursorStyle, lastStyle); + lastSelectionStyle, lastStyle); } lastStyle = style; - lastCursorStyle = cursorStyle; + lastSelectionStyle = selectionStyle; runWidth = 0; lastRunStart = column; lastRunStartIndex = index; @@ -255,7 +255,7 @@ public final void drawText(int row, Canvas canvas, float x, float y, renderer.drawTextRun(canvas, x, y, lastRunStart, runWidth, line, lastRunStartIndex, index - lastRunStartIndex, - lastCursorStyle, lastStyle); + lastSelectionStyle, lastStyle); } if (cx >= 0 && imeText.length() > 0) { @@ -422,18 +422,18 @@ public void resize(int columns, int rows, int style) { */ public char[] getScriptLine(int row) { - try - { - return mData.getLine(row); - } - catch (IllegalArgumentException e) - { - return null; - } - catch (NullPointerException e) - { - return null; - } + try + { + return mData.getLine(row); + } + catch (IllegalArgumentException e) + { + return null; + } + catch (NullPointerException e) + { + return null; + } } /** @@ -443,6 +443,6 @@ public char[] getScriptLine(int row) */ public boolean getScriptLineWrap(int row) { - return mData.getLineWrap(row); + return mData.getLineWrap(row); } } From 9f1d71b71bd65f055824c20e7578d609609b2685 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 29 Mar 2014 18:16:36 -0700 Subject: [PATCH 644/847] Make cursor compatible with hardware graphics. The PixelXorXfermode that we had previously been using is not compatible with hardware rendering. Now we draw an off-screen alpha mask that's the desired shape, and use that to draw the cursor on screen. --- .../emulatorview/BaseTextRenderer.java | 69 +++++++++++++++---- .../emulatorview/Bitmap4x8FontRenderer.java | 22 +++--- .../emulatorview/PaintRenderer.java | 9 ++- .../emulatorview/TextRenderer.java | 15 ++-- .../emulatorview/TranscriptScreen.java | 23 ++++--- 5 files changed, 97 insertions(+), 41 deletions(-) diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/BaseTextRenderer.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/BaseTextRenderer.java index 5fbd504fa..efad0d177 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/BaseTextRenderer.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/BaseTextRenderer.java @@ -16,7 +16,10 @@ package jackpal.androidterm.emulatorview; +import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.ColorMatrix; +import android.graphics.ColorMatrixColorFilter; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; @@ -294,10 +297,12 @@ abstract class BaseTextRenderer implements TextRenderer { 0xffeeeeee }; - private final static int sCursorColor = 0xff808080; + protected final static int sCursorColor = 0xff808080; static final ColorScheme defaultColorScheme = new ColorScheme(0xffcccccc, 0xff000000); + private final Paint mCursorScreenPaint; + private final Paint mCopyRedToAlphaPaint; private final Paint mCursorPaint; private final Paint mCursorStrokePaint; private final Path mShiftCursor; @@ -311,6 +316,9 @@ abstract class BaseTextRenderer implements TextRenderer { private float mLastCharHeight; private static final Matrix.ScaleToFit mScaleType = Matrix.ScaleToFit.FILL; + private Bitmap mCursorBitmap; + private Bitmap mWorkBitmap; + private int mCursorBitmapCursorMode = -1; public BaseTextRenderer(ColorScheme scheme) { if (scheme == null) { @@ -318,15 +326,26 @@ public BaseTextRenderer(ColorScheme scheme) { } setDefaultColors(scheme.getForeColor(), scheme.getBackColor()); + mCursorScreenPaint = new Paint(); + mCursorScreenPaint.setColor(sCursorColor); + mCursorPaint = new Paint(); - mCursorPaint.setColor(sCursorColor); - mCursorPaint.setXfermode(new PixelXorXfermode(~sCursorColor)); + mCursorPaint.setColor(0xff000000); // Opaque black mCursorPaint.setAntiAlias(true); mCursorStrokePaint = new Paint(mCursorPaint); mCursorStrokePaint.setStrokeWidth(0.1f); mCursorStrokePaint.setStyle(Paint.Style.STROKE); + mCopyRedToAlphaPaint = new Paint(); + ColorMatrix cm = new ColorMatrix(); + cm.set(new float[] { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0 }); + mCopyRedToAlphaPaint.setColorFilter(new ColorMatrixColorFilter(cm)); + mShiftCursor = new Path(); mShiftCursor.lineTo(0.5f, 0.33f); mShiftCursor.lineTo(1.0f, 0.0f); @@ -373,25 +392,45 @@ private static int[] cloneDefaultColors() { protected void drawCursorImp(Canvas canvas, float x, float y, float charWidth, float charHeight, int cursorMode) { + if (cursorMode == 0) { + canvas.drawRect(x, y - charHeight, x + charWidth, y, mCursorScreenPaint); + return; + } + + // Fancy cursor. Draw an offscreen cursor shape, then blit it on screen. + + // Has the character size changed? + if (charWidth != mLastCharWidth || charHeight != mLastCharHeight) { mLastCharWidth = charWidth; mLastCharHeight = charHeight; mTempDst.set(0.0f, 0.0f, charWidth, charHeight); mScaleMatrix.setRectToRect(mTempSrc, mTempDst, mScaleType); + mCursorBitmap = Bitmap.createBitmap((int) charWidth, (int) charHeight, + Bitmap.Config.ALPHA_8); + mWorkBitmap = Bitmap.createBitmap((int) charWidth, (int) charHeight, + Bitmap.Config.ARGB_8888); + mCursorBitmapCursorMode = -1; } - canvas.save(); - canvas.translate(x, y - charHeight); - canvas.clipRect(0, 0, charWidth, charHeight); - canvas.drawPaint(mCursorPaint); - - if (cursorMode != 0) { - canvas.concat(mScaleMatrix); - drawCursorHelper(canvas, mShiftCursor, cursorMode, MODE_SHIFT_SHIFT); - drawCursorHelper(canvas, mAltCursor, cursorMode, MODE_ALT_SHIFT); - drawCursorHelper(canvas, mCtrlCursor, cursorMode, MODE_CTRL_SHIFT); - drawCursorHelper(canvas, mFnCursor, cursorMode, MODE_FN_SHIFT); + + // Has the cursor mode changed ? + + if (cursorMode != mCursorBitmapCursorMode) { + mCursorBitmapCursorMode = cursorMode; + mWorkBitmap.eraseColor(0xffffffff); + Canvas workCanvas = new Canvas(mWorkBitmap); + workCanvas.concat(mScaleMatrix); + drawCursorHelper(workCanvas, mShiftCursor, cursorMode, MODE_SHIFT_SHIFT); + drawCursorHelper(workCanvas, mAltCursor, cursorMode, MODE_ALT_SHIFT); + drawCursorHelper(workCanvas, mCtrlCursor, cursorMode, MODE_CTRL_SHIFT); + drawCursorHelper(workCanvas, mFnCursor, cursorMode, MODE_FN_SHIFT); + + mCursorBitmap.eraseColor(0); + Canvas bitmapCanvas = new Canvas(mCursorBitmap); + bitmapCanvas.drawBitmap(mWorkBitmap, 0, 0, mCopyRedToAlphaPaint); } - canvas.restore(); + + canvas.drawBitmap(mCursorBitmap, x, y - charHeight, mCursorScreenPaint); } private void drawCursorHelper(Canvas canvas, Path path, int mode, int shift) { diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/Bitmap4x8FontRenderer.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/Bitmap4x8FontRenderer.java index c4659a9da..d51d10ecd 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/Bitmap4x8FontRenderer.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/Bitmap4x8FontRenderer.java @@ -17,6 +17,7 @@ package jackpal.androidterm.emulatorview; import jackpal.androidterm.emulatorview.compat.AndroidCompat; + import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -61,7 +62,8 @@ public int getTopMargin() { public void drawTextRun(Canvas canvas, float x, float y, int lineOffset, int runWidth, char[] text, int index, int count, - boolean selectionStyle, int textStyle) { + boolean selectionStyle, int textStyle, + int cursorOffset, int cursorWidth, int cursorMode) { int foreColor = TextStyle.decodeForeColor(textStyle); int backColor = TextStyle.decodeBackColor(textStyle); int effect = TextStyle.decodeEffect(textStyle); @@ -95,6 +97,17 @@ public void drawTextRun(Canvas canvas, float x, float y, foreColor = backColor; } + drawTextRunHelper(canvas, x, y, lineOffset, text, index, count, foreColor, backColor); + + // The cursor is too small to show the cursor mode. + if (lineOffset <= cursorOffset && cursorOffset < (lineOffset + count)) { + drawTextRunHelper(canvas, x, y, cursorOffset, text, cursorOffset-lineOffset, 1, + foreColor, TextStyle.ciCursor); + } + } + + private void drawTextRunHelper(Canvas canvas, float x, float y, int lineOffset, char[] text, + int index, int count, int foreColor, int backColor) { setColorMatrix(mPalette[foreColor], mPalette[backColor]); int destX = (int) x + kCharacterWidth * lineOffset; int destY = (int) y; @@ -121,13 +134,6 @@ public void drawTextRun(Canvas canvas, float x, float y, } } - public void drawCursor(Canvas canvas, float x, float y, int lineOffset, - int width, int cursorMode) { - int destX = (int) x + kCharacterWidth * lineOffset; - int destY = (int) y; - drawCursorImp(canvas, destX, destY, kCharacterWidth, kCharacterHeight, cursorMode); - } - private void setColorMatrix(int foreColor, int backColor) { if ((foreColor != mCurrentForeColor) || (backColor != mCurrentBackColor) diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/PaintRenderer.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/PaintRenderer.java index bb7a86557..f59ca370c 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/PaintRenderer.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/PaintRenderer.java @@ -38,7 +38,8 @@ public PaintRenderer(int fontSize, ColorScheme scheme) { public void drawTextRun(Canvas canvas, float x, float y, int lineOffset, int runWidth, char[] text, int index, int count, - boolean selectionStyle, int textStyle) { + boolean selectionStyle, int textStyle, + int cursorOffset, int cursorWidth, int cursorMode) { int foreColor = TextStyle.decodeForeColor(textStyle); int backColor = TextStyle.decodeBackColor(textStyle); int effect = TextStyle.decodeEffect(textStyle); @@ -65,6 +66,12 @@ public void drawTextRun(Canvas canvas, float x, float y, int lineOffset, canvas.drawRect(left, y + mCharAscent - mCharDescent, left + runWidth * mCharWidth, y, mTextPaint); + + if (index <= cursorOffset && cursorOffset < (index + count)) { + int cursorX = (int) (x + cursorOffset * mCharWidth); + drawCursorImp(canvas, cursorX, y, mCharWidth, mCharHeight, cursorMode); + } + boolean invisible = (effect & TextStyle.fxInvisible) != 0; if (!invisible) { boolean bold = (effect & TextStyle.fxBold) != 0; diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TextRenderer.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TextRenderer.java index 734dc7d8f..a62b08b07 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TextRenderer.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TextRenderer.java @@ -41,20 +41,21 @@ interface TextRenderer { /** * Draw a run of text * @param canvas The canvas to draw into. - * @param x The x coordinate. - * @param y The y coordinate. - * @param lineOffset + * @param x Canvas coordinate of the left edge of the whole line. + * @param y Canvas coordinate of the bottom edge of the whole line. + * @param lineOffset The screen character offset of this text run (0..length of line) * @param runWidth * @param text * @param index * @param count * @param selectionStyle True to draw the text using the "selected" style (for clipboard copy) * @param textStyle + * @param cursorOffset The screen character offset of the cursor (or -1 if not on this line.) + * @param cursorWidth The width of the cursor in screen characters (1 or 2) + * @param cursorMode The cursor mode (used to show state of shift/control/alt/fn locks. */ void drawTextRun(Canvas canvas, float x, float y, int lineOffset, int runWidth, char[] text, - int index, int count, boolean selectionStyle, int textStyle); - // width is width in characters. (1 for normal 2 for wide.) - void drawCursor(Canvas canvas, float x, float y, int lineOffset, - int width, int cursorMode); + int index, int count, boolean selectionStyle, int textStyle, + int cursorOffset, int cursorWidth, int cursorMode); } diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TranscriptScreen.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TranscriptScreen.java index 2dd0f4857..d8e150d33 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TranscriptScreen.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TranscriptScreen.java @@ -188,10 +188,14 @@ public final void drawText(int row, Canvas canvas, float x, float y, char[] blank = new char[selx2-selx1]; Arrays.fill(blank, ' '); renderer.drawTextRun(canvas, x, y, selx1, selx2-selx1, - blank, 0, 1, true, defaultStyle); - } else if (cx != -1) { + blank, 0, 1, true, defaultStyle, cx, cursorWidth, cursorMode); + } + if (cx != -1) { + char[] blank = new char[1]; + Arrays.fill(blank, ' '); // We need to draw the cursor - renderer.drawCursor(canvas, x, y, cx, cursorWidth, cursorMode); + renderer.drawTextRun(canvas, x, y, cx, 1, + blank, 0, 1, true, defaultStyle, cx, 1, cursorMode); } return; @@ -228,7 +232,8 @@ public final void drawText(int row, Canvas canvas, float x, float y, renderer.drawTextRun(canvas, x, y, lastRunStart, runWidth, line, lastRunStartIndex, index - lastRunStartIndex, - lastSelectionStyle, lastStyle); + lastSelectionStyle, lastStyle, + cx, cursorWidth, cursorMode); } lastStyle = style; lastSelectionStyle = selectionStyle; @@ -255,7 +260,8 @@ public final void drawText(int row, Canvas canvas, float x, float y, renderer.drawTextRun(canvas, x, y, lastRunStart, runWidth, line, lastRunStartIndex, index - lastRunStartIndex, - lastSelectionStyle, lastStyle); + lastSelectionStyle, lastStyle, + cx, cursorWidth, cursorMode); } if (cx >= 0 && imeText.length() > 0) { @@ -263,11 +269,8 @@ public final void drawText(int row, Canvas canvas, float x, float y, int imeOffset = imeText.length() - imeLength; int imePosition = Math.min(cx, columns - imeLength); renderer.drawTextRun(canvas, x, y, imePosition, imeLength, imeText.toCharArray(), - imeOffset, imeLength, true, TextStyle.encode(0x0f, 0x00, TextStyle.fxNormal)); - } - - if (cx >= 0) { - renderer.drawCursor(canvas, x, y, cx, cursorWidth, cursorMode); + imeOffset, imeLength, true, TextStyle.encode(0x0f, 0x00, TextStyle.fxNormal), + cx, cursorWidth, cursorMode); } } From e7b5f51f05a789bc8aa737ce10142e42bdcd4aba Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 29 Mar 2014 18:22:35 -0700 Subject: [PATCH 645/847] Re-enable hardware accelerated text drawing. --- AndroidManifest.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index f9a4a2b26..3a8662690 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -25,7 +25,8 @@ android:permissionGroup="android.permission-group.SYSTEM_TOOLS" android:protectionLevel="dangerous" /> + android:label="@string/application_terminal" + android:hardwareAccelerated="true"> + android:label="@string/preferences"/> From accdf8bc61794cc82fa31cdcfeeba02f552a6c07 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 29 Mar 2014 21:58:48 -0700 Subject: [PATCH 646/847] Algorithmicly choose cursor foreground color. --- .../emulatorview/BaseTextRenderer.java | 19 ++--- .../emulatorview/Bitmap4x8FontRenderer.java | 4 +- .../androidterm/emulatorview/ColorScheme.java | 74 ++++++++++++++++++- .../emulatorview/PaintRenderer.java | 42 ++++++++--- .../androidterm/emulatorview/TextStyle.java | 5 +- 5 files changed, 116 insertions(+), 28 deletions(-) diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/BaseTextRenderer.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/BaseTextRenderer.java index efad0d177..5542ec111 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/BaseTextRenderer.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/BaseTextRenderer.java @@ -23,7 +23,6 @@ import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; -import android.graphics.PixelXorXfermode; import android.graphics.RectF; abstract class BaseTextRenderer implements TextRenderer { @@ -297,7 +296,6 @@ abstract class BaseTextRenderer implements TextRenderer { 0xffeeeeee }; - protected final static int sCursorColor = 0xff808080; static final ColorScheme defaultColorScheme = new ColorScheme(0xffcccccc, 0xff000000); @@ -324,13 +322,15 @@ public BaseTextRenderer(ColorScheme scheme) { if (scheme == null) { scheme = defaultColorScheme; } - setDefaultColors(scheme.getForeColor(), scheme.getBackColor()); + setDefaultColors(scheme); mCursorScreenPaint = new Paint(); - mCursorScreenPaint.setColor(sCursorColor); + mCursorScreenPaint.setColor(scheme.getCursorBackColor()); + // Cursor paint and cursor stroke paint are used to draw a grayscale mask that's converted + // to an alpha8 texture. Only the red channel's value matters. mCursorPaint = new Paint(); - mCursorPaint.setColor(0xff000000); // Opaque black + mCursorPaint.setColor(0xff909090); // Opaque lightgray mCursorPaint.setAntiAlias(true); mCursorStrokePaint = new Paint(mCursorPaint); @@ -376,11 +376,12 @@ public void setReverseVideo(boolean reverseVideo) { mReverseVideo = reverseVideo; } - private void setDefaultColors(int forePaintColor, int backPaintColor) { + private void setDefaultColors(ColorScheme scheme) { mPalette = cloneDefaultColors(); - mPalette[TextStyle.ciForeground] = forePaintColor; - mPalette[TextStyle.ciBackground] = backPaintColor; - mPalette[TextStyle.ciCursor] = sCursorColor; + mPalette[TextStyle.ciForeground] = scheme.getForeColor(); + mPalette[TextStyle.ciBackground] = scheme.getBackColor(); + mPalette[TextStyle.ciCursorForeground] = scheme.getCursorForeColor(); + mPalette[TextStyle.ciCursorBackground] = scheme.getCursorBackColor(); } private static int[] cloneDefaultColors() { diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/Bitmap4x8FontRenderer.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/Bitmap4x8FontRenderer.java index d51d10ecd..fbdddb8d9 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/Bitmap4x8FontRenderer.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/Bitmap4x8FontRenderer.java @@ -88,7 +88,7 @@ public void drawTextRun(Canvas canvas, float x, float y, } if (selectionStyle) { - backColor = TextStyle.ciCursor; + backColor = TextStyle.ciCursorBackground; } boolean invisible = (effect & TextStyle.fxInvisible) != 0; @@ -102,7 +102,7 @@ public void drawTextRun(Canvas canvas, float x, float y, // The cursor is too small to show the cursor mode. if (lineOffset <= cursorOffset && cursorOffset < (lineOffset + count)) { drawTextRunHelper(canvas, x, y, cursorOffset, text, cursorOffset-lineOffset, 1, - foreColor, TextStyle.ciCursor); + TextStyle.ciCursorForeground, TextStyle.ciCursorBackground); } } diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/ColorScheme.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/ColorScheme.java index 0e0465d7f..e264695a1 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/ColorScheme.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/ColorScheme.java @@ -44,6 +44,34 @@ public class ColorScheme { private int foreColor; private int backColor; + private int cursorForeColor; + private int cursorBackColor; + final private static int sDefaultCursorBackColor = 0xff808080; + + private void setDefaultCursorColors() { + cursorBackColor = sDefaultCursorBackColor; + // Use the foreColor unless the foreColor is too similar to the cursorBackColor + int foreDistance = distance(foreColor, cursorBackColor); + int backDistance = distance(backColor, cursorBackColor); + if (foreDistance * 2 >= backDistance) { + cursorForeColor = foreColor; + } else { + cursorForeColor = backColor; + } + } + + private static int distance(int a, int b) { + return channelDistance(a, b, 0) * 3 + channelDistance(a, b, 1) * 5 + + channelDistance(a, b, 2); + } + + private static int channelDistance(int a, int b, int channel) { + return Math.abs(getChannel(a, channel) - getChannel(b, channel)); + } + + private static int getChannel(int color, int channel) { + return 0xff & (color >> ((2 - channel) * 8)); + } /** * Creates a ColorScheme object. @@ -54,21 +82,43 @@ public class ColorScheme { public ColorScheme(int foreColor, int backColor) { this.foreColor = foreColor; this.backColor = backColor; + setDefaultCursorColors(); + } + + /** + * Creates a ColorScheme object. + * + * @param foreColor The foreground color as an ARGB hex value. + * @param backColor The background color as an ARGB hex value. + * @param cursorForeColor The cursor foreground color as an ARGB hex value. + * @param cursorBackColor The cursor foreground color as an ARGB hex value. + */ + public ColorScheme(int foreColor, int backColor, int cursorForeColor, int cursorBackColor) { + this.foreColor = foreColor; + this.backColor = backColor; + this.cursorForeColor = cursorForeColor; + this.cursorBackColor = cursorBackColor; } /** * Creates a ColorScheme object from an array. * - * @param scheme An integer array { foreColorIndex, foreColor, - * backColorIndex, backColor }. + * @param scheme An integer array { foreColor, backColor, + * optionalCursorForeColor, optionalCursorBackColor }. */ public ColorScheme(int[] scheme) { - if (scheme.length != 2) { + int schemeLength = scheme.length; + if (schemeLength != 2 && schemeLength != 4) { throw new IllegalArgumentException(); } - this.foreColor = scheme[0]; this.backColor = scheme[1]; + if (schemeLength == 2) { + setDefaultCursorColors(); + } else { + this.cursorForeColor = scheme[2]; + this.cursorBackColor = scheme[3]; + } } /** @@ -86,4 +136,20 @@ public int getForeColor() { public int getBackColor() { return backColor; } + + /** + * @return This ColorScheme's cursor foreground color as an ARGB + * hex value. + */ + public int getCursorForeColor() { + return cursorForeColor; + } + + /** + * @return This ColorScheme's cursor background color as an ARGB + * hex value. + */ + public int getCursorBackColor() { + return cursorBackColor; + } } diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/PaintRenderer.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/PaintRenderer.java index f59ca370c..9caaa6d25 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/PaintRenderer.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/PaintRenderer.java @@ -53,7 +53,7 @@ public void drawTextRun(Canvas canvas, float x, float y, int lineOffset, } if (selectionStyle) { - backColor = TextStyle.ciCursor; + backColor = TextStyle.ciCursorBackground; } boolean blink = (effect & TextStyle.fxBlink) != 0; @@ -67,7 +67,8 @@ public void drawTextRun(Canvas canvas, float x, float y, int lineOffset, left + runWidth * mCharWidth, y, mTextPaint); - if (index <= cursorOffset && cursorOffset < (index + count)) { + boolean cursorVisible = index <= cursorOffset && cursorOffset < (index + count); + if (cursorVisible) { int cursorX = (int) (x + cursorOffset * mCharWidth); drawCursorImp(canvas, cursorX, y, mCharWidth, mCharHeight, cursorMode); } @@ -82,13 +83,38 @@ public void drawTextRun(Canvas canvas, float x, float y, int lineOffset, if (underline) { mTextPaint.setUnderlineText(true); } + int textPaintColor; if (foreColor < 8 && bold) { // In 16-color mode, bold also implies bright foreground colors - mTextPaint.setColor(mPalette[foreColor+8]); + textPaintColor = mPalette[foreColor+8]; } else { - mTextPaint.setColor(mPalette[foreColor]); + textPaintColor = mPalette[foreColor]; + } + mTextPaint.setColor(textPaintColor); + + float textOriginY = y - mCharDescent; + + if (cursorVisible) { + // Text before cursor + int countBeforeCursor = cursorOffset - index; + int countAfterCursor = count - (countBeforeCursor + 1); + if (countBeforeCursor > 0){ + canvas.drawText(text, index, countBeforeCursor, left, textOriginY, mTextPaint); + } + // Text at cursor + mTextPaint.setColor(mPalette[TextStyle.ciCursorForeground]); + canvas.drawText(text, cursorOffset, 1, x + cursorOffset * mCharWidth, + textOriginY, mTextPaint); + // Text after cursor + if (countAfterCursor > 0) { + mTextPaint.setColor(textPaintColor); + canvas.drawText(text, cursorOffset + 1, countAfterCursor, + x + (cursorOffset + 1) * mCharWidth, + textOriginY, mTextPaint); + } + } else { + canvas.drawText(text, index, count, left, textOriginY, mTextPaint); } - canvas.drawText(text, index, count, left, y - mCharDescent, mTextPaint); if (bold) { mTextPaint.setFakeBoldText(false); } @@ -98,12 +124,6 @@ public void drawTextRun(Canvas canvas, float x, float y, int lineOffset, } } - public void drawCursor(Canvas canvas, float x, float y, int lineOffset, - int width, int cursorMode) { - float left = x + lineOffset * mCharWidth; - drawCursorImp(canvas, left, y, mCharWidth * width, mCharHeight, cursorMode); - } - public int getCharacterHeight() { return mCharHeight; } diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TextStyle.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TextStyle.java index 7e0f1eb24..1f2022d69 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TextStyle.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TextStyle.java @@ -14,9 +14,10 @@ public final class TextStyle { // Special color indices final static int ciForeground = 256; // VT100 text foreground color final static int ciBackground = 257; // VT100 text background color - final static int ciCursor = 258; // VT100 text cursor color + final static int ciCursorForeground = 258; // VT100 text cursor foreground color + final static int ciCursorBackground = 259; // VT100 text cursor background color - final static int ciColorLength = ciCursor + 1; + final static int ciColorLength = ciCursorBackground + 1; final static int kNormalTextStyle = encode(ciForeground, ciBackground, fxNormal); From e69a1b44450002c7f60c2e5c8e53dceae905a5bb Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 29 Mar 2014 23:09:05 -0700 Subject: [PATCH 647/847] Fix cursor rendering for wide characters. --- .../emulatorview/Bitmap4x8FontRenderer.java | 2 +- .../androidterm/emulatorview/PaintRenderer.java | 12 ++++++------ .../androidterm/emulatorview/TextRenderer.java | 6 ++++-- .../emulatorview/TranscriptScreen.java | 16 +++++++++++----- 4 files changed, 22 insertions(+), 14 deletions(-) diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/Bitmap4x8FontRenderer.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/Bitmap4x8FontRenderer.java index fbdddb8d9..f2282fd21 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/Bitmap4x8FontRenderer.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/Bitmap4x8FontRenderer.java @@ -63,7 +63,7 @@ public int getTopMargin() { public void drawTextRun(Canvas canvas, float x, float y, int lineOffset, int runWidth, char[] text, int index, int count, boolean selectionStyle, int textStyle, - int cursorOffset, int cursorWidth, int cursorMode) { + int cursorOffset, int cursorIndex, int cursorIncr, int cursorWidth, int cursorMode) { int foreColor = TextStyle.decodeForeColor(textStyle); int backColor = TextStyle.decodeBackColor(textStyle); int effect = TextStyle.decodeEffect(textStyle); diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/PaintRenderer.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/PaintRenderer.java index 9caaa6d25..9593c0cf2 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/PaintRenderer.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/PaintRenderer.java @@ -39,7 +39,7 @@ public PaintRenderer(int fontSize, ColorScheme scheme) { public void drawTextRun(Canvas canvas, float x, float y, int lineOffset, int runWidth, char[] text, int index, int count, boolean selectionStyle, int textStyle, - int cursorOffset, int cursorWidth, int cursorMode) { + int cursorOffset, int cursorIndex, int cursorIncr, int cursorWidth, int cursorMode) { int foreColor = TextStyle.decodeForeColor(textStyle); int backColor = TextStyle.decodeBackColor(textStyle); int effect = TextStyle.decodeEffect(textStyle); @@ -70,7 +70,7 @@ public void drawTextRun(Canvas canvas, float x, float y, int lineOffset, boolean cursorVisible = index <= cursorOffset && cursorOffset < (index + count); if (cursorVisible) { int cursorX = (int) (x + cursorOffset * mCharWidth); - drawCursorImp(canvas, cursorX, y, mCharWidth, mCharHeight, cursorMode); + drawCursorImp(canvas, cursorX, y, cursorWidth * mCharWidth, mCharHeight, cursorMode); } boolean invisible = (effect & TextStyle.fxInvisible) != 0; @@ -96,20 +96,20 @@ public void drawTextRun(Canvas canvas, float x, float y, int lineOffset, if (cursorVisible) { // Text before cursor - int countBeforeCursor = cursorOffset - index; + int countBeforeCursor = cursorIndex - index; int countAfterCursor = count - (countBeforeCursor + 1); if (countBeforeCursor > 0){ canvas.drawText(text, index, countBeforeCursor, left, textOriginY, mTextPaint); } // Text at cursor mTextPaint.setColor(mPalette[TextStyle.ciCursorForeground]); - canvas.drawText(text, cursorOffset, 1, x + cursorOffset * mCharWidth, + canvas.drawText(text, cursorIndex, cursorIncr, x + cursorOffset * mCharWidth, textOriginY, mTextPaint); // Text after cursor if (countAfterCursor > 0) { mTextPaint.setColor(textPaintColor); - canvas.drawText(text, cursorOffset + 1, countAfterCursor, - x + (cursorOffset + 1) * mCharWidth, + canvas.drawText(text, cursorIndex + cursorIncr, countAfterCursor, + x + (cursorOffset + cursorWidth) * mCharWidth, textOriginY, mTextPaint); } } else { diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TextRenderer.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TextRenderer.java index a62b08b07..39c615da8 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TextRenderer.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TextRenderer.java @@ -51,11 +51,13 @@ interface TextRenderer { * @param selectionStyle True to draw the text using the "selected" style (for clipboard copy) * @param textStyle * @param cursorOffset The screen character offset of the cursor (or -1 if not on this line.) - * @param cursorWidth The width of the cursor in screen characters (1 or 2) + * @param cursorIndex The index of the cursor in text chars. + * @param cursorIncr The width of the cursor in text chars. (1 or 2) + * @param cursorWidth The width of the cursor in screen columns (1 or 2) * @param cursorMode The cursor mode (used to show state of shift/control/alt/fn locks. */ void drawTextRun(Canvas canvas, float x, float y, int lineOffset, int runWidth, char[] text, int index, int count, boolean selectionStyle, int textStyle, - int cursorOffset, int cursorWidth, int cursorMode); + int cursorOffset, int cursorIndex, int cursorIncr, int cursorWidth, int cursorMode); } diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TranscriptScreen.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TranscriptScreen.java index d8e150d33..60fca45dd 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TranscriptScreen.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TranscriptScreen.java @@ -188,14 +188,16 @@ public final void drawText(int row, Canvas canvas, float x, float y, char[] blank = new char[selx2-selx1]; Arrays.fill(blank, ' '); renderer.drawTextRun(canvas, x, y, selx1, selx2-selx1, - blank, 0, 1, true, defaultStyle, cx, cursorWidth, cursorMode); + blank, 0, 1, true, defaultStyle, + cx, 0, 1, 1, cursorMode); } if (cx != -1) { char[] blank = new char[1]; Arrays.fill(blank, ' '); // We need to draw the cursor renderer.drawTextRun(canvas, x, y, cx, 1, - blank, 0, 1, true, defaultStyle, cx, 1, cursorMode); + blank, 0, 1, true, defaultStyle, + cx, 0, 1, 1, cursorMode); } return; @@ -210,6 +212,8 @@ public final void drawText(int row, Canvas canvas, float x, float y, boolean forceFlushRun = false; int column = 0; int index = 0; + int cursorIndex = 0; + int cursorIncr = 0; while (column < columns) { int style = color.get(column); boolean selectionStyle = false; @@ -233,7 +237,7 @@ public final void drawText(int row, Canvas canvas, float x, float y, line, lastRunStartIndex, index - lastRunStartIndex, lastSelectionStyle, lastStyle, - cx, cursorWidth, cursorMode); + cx, cursorIndex, cursorIncr, cursorWidth, cursorMode); } lastStyle = style; lastSelectionStyle = selectionStyle; @@ -243,6 +247,8 @@ public final void drawText(int row, Canvas canvas, float x, float y, forceFlushRun = false; } if (cx == column) { + cursorIndex = index; + cursorIncr = incr; cursorWidth = width; } runWidth += width; @@ -261,7 +267,7 @@ public final void drawText(int row, Canvas canvas, float x, float y, line, lastRunStartIndex, index - lastRunStartIndex, lastSelectionStyle, lastStyle, - cx, cursorWidth, cursorMode); + cx, cursorIndex, cursorIncr, cursorWidth, cursorMode); } if (cx >= 0 && imeText.length() > 0) { @@ -270,7 +276,7 @@ public final void drawText(int row, Canvas canvas, float x, float y, int imePosition = Math.min(cx, columns - imeLength); renderer.drawTextRun(canvas, x, y, imePosition, imeLength, imeText.toCharArray(), imeOffset, imeLength, true, TextStyle.encode(0x0f, 0x00, TextStyle.fxNormal), - cx, cursorWidth, cursorMode); + -1, 0, 0, 0, 0); } } From 25cc8fc1018990b51744cabb38c42d007de1930b Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sun, 30 Mar 2014 12:46:32 -0700 Subject: [PATCH 648/847] v1.0.59 --- AndroidManifest.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 3a8662690..1fbf4d421 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,7 +1,7 @@ From 8c3977f281a4a4c0f7ebd1bcc9edebc818057dd3 Mon Sep 17 00:00:00 2001 From: "Frank P. Westlake" Date: Thu, 3 Apr 2014 12:49:53 -0700 Subject: [PATCH 649/847] Update AndroidManifest.xml These additions allow multi-window devices to resize ATE so that it can be displayed with one other application. These windows are resizable by the user and simply touching one or the other changes the focus. The effect, with ATE as one of the applications, can be that of an IDE if the other application is a script editor. One such multi-window device is the Galaxy Note 3. --- AndroidManifest.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 1fbf4d421..08eca27b5 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -35,6 +35,7 @@ + + + From 913955070947e9adb27637bd203490c6767eb045 Mon Sep 17 00:00:00 2001 From: Spanti Nicola Date: Fri, 11 Apr 2014 18:42:00 -0400 Subject: [PATCH 650/847] A little bit more french --- res/values-fr/arrays.xml | 2 +- res/values-fr/strings.xml | 29 +++++++++++++++++++++-------- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/res/values-fr/arrays.xml b/res/values-fr/arrays.xml index 62bab19b8..6a6b46fdc 100644 --- a/res/values-fr/arrays.xml +++ b/res/values-fr/arrays.xml @@ -53,7 +53,7 @@ Texte rouge sur noir Solarized Light Solarized Dark - Linux Console + Console Linux diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml index 66f7fce56..d93f95d06 100644 --- a/res/values-fr/strings.xml +++ b/res/values-fr/strings.xml @@ -26,17 +26,17 @@ Terminal par défaut Envoyer un e-mail Touches spéciales - Afficher/Masquer Clavier + Afficher/Masquer clavier État de cette fenêtre par défaut. Bloquer la mise en veille Débloquer la mise en veille - Bloquer le Wifi - Débloquer le Wifi + Bloquer le WiFi + Débloquer le WiFi Modifier le texte - Selectionner le texte + Sélectionner le texte Tout copier Coller Touche Control @@ -115,7 +115,7 @@ Type de terminal Type de terminal à reporter au shell - Type de Terminal + Type de terminal Quitter avec exit Quitter la fenêtre en cours en exécutant la commande exit @@ -124,10 +124,13 @@ Les répertoires inaccessibles seront retirés de PATH Extensions de PATH - D\'autres application sont autorisées à fournir des commandes supplémentaires (à ajouter au PATH) + D\'autres applications sont autorisées à fournir des commandes supplémentaires (à ajouter au PATH) Préfixe de PATH D\'autres applications sont autorisées à remplacer les commandes existantes (à ajouter au début de PATH) + + Dossier HOME + Chemin à un dossier accessible en écriture pour être utilisé comme HOME. Touches Control et Fonction @@ -138,13 +141,23 @@ Touche fonction non paramètrée. Transcription depuis Terminal Emulator - La transcription par email utilise: + La transcription par email utilise : Impossible de choisir une activité de messagerie pour envoyer la transcription Lancer des commandes tierces - Permettre à une application d\'ouvrir une nouvelle fenêtre dans Terminal Emulator en exécutant des commandes avec les permissions de l\'application comme l\'accès à internet et à votre carte SD + Permettre à une application d\'ouvrir une nouvelle fenêtre dans Terminal Emulator en exécutant des commandes avec les permissions de l\'application comme l\'accès à Internet et à votre carte SD Ajouter des commandes Permettre à une application de fournir des commandes supplémentaires (ajouter le répertoire au PATH) pour Terminal Emulator Remplacer des commandes Permettre à une application de remplacer des commandes existantes avec ses versions (répertoire à ajouter en préfixe au PATH) dans Terminal Emulator + + Envoyer des événements de souris + Aussi bien les événements d\'appui et de scroll doivent être envoyés sous forme de séquences d'échappement au terminal. + + Utiliser les raccourcis clavier + Ctrl-Tab: cycle window, Ctrl-Shift-N: nouvelle fenêtre, Ctrl-Shift-V: coller. + Raccourcis clavier désactivés. + + Aide + From 60f5dd963464aa733c14d844c9b2f0414888cc79 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sat, 12 Apr 2014 07:33:29 -0700 Subject: [PATCH 651/847] Add a backslash before an apostrophe. Required to compile the resource. --- res/values-fr/strings.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml index d93f95d06..cf664cc04 100644 --- a/res/values-fr/strings.xml +++ b/res/values-fr/strings.xml @@ -128,7 +128,7 @@ Préfixe de PATH D\'autres applications sont autorisées à remplacer les commandes existantes (à ajouter au début de PATH) - + Dossier HOME Chemin à un dossier accessible en écriture pour être utilisé comme HOME. @@ -143,7 +143,7 @@ Transcription depuis Terminal Emulator La transcription par email utilise : Impossible de choisir une activité de messagerie pour envoyer la transcription - + Lancer des commandes tierces Permettre à une application d\'ouvrir une nouvelle fenêtre dans Terminal Emulator en exécutant des commandes avec les permissions de l\'application comme l\'accès à Internet et à votre carte SD Ajouter des commandes @@ -152,8 +152,8 @@ Permettre à une application de remplacer des commandes existantes avec ses versions (répertoire à ajouter en préfixe au PATH) dans Terminal Emulator Envoyer des événements de souris - Aussi bien les événements d\'appui et de scroll doivent être envoyés sous forme de séquences d'échappement au terminal. - + Aussi bien les événements d\'appui et de scroll doivent être envoyés sous forme de séquences d\'échappement au terminal. + Utiliser les raccourcis clavier Ctrl-Tab: cycle window, Ctrl-Shift-N: nouvelle fenêtre, Ctrl-Shift-V: coller. Raccourcis clavier désactivés. From c0dbbc791a08ddb8b97d00e177063253775c0140 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sun, 20 Apr 2014 11:11:46 -0700 Subject: [PATCH 652/847] Add "Term Here" send intent to open a terminal in a given directory. This addition adds "Term here" to the "Send" or "Send to" option on many file managers, accessable by a long press. Long-press a file or directory in a file manager and select "Term here"; Term will open to a new window at that location. The permission.RUN_SCRIPT permission is not used because no executables other than 'cd' are invoked. Choosing "Term here" performs essentially the same action as merely opening Term, but with the addition of the specified starting directory. I chose to use the "send" intent option instead of a normal intent with 'data' because I think showing "Term here" for every file you wish to open normally is excessive, so for the special case where you want to open a terminal at a file or directory location the "send" feature can be used. It might be desired to later add a the ability to launch scripts from a file manager by selecting the script, or other executable, and choosing "Term", or "Run with Term". This would be implemented with a normal Intent which uses 'data' to communicate the file and would be selected after a short-press in most or all file managers. Original patch by Frank Westlake. Improved formatting, error checking, and shell quoting by jackpal. Tested in "ES File Explorer File Manager". --- AndroidManifest.xml | 11 +++++++ src/jackpal/androidterm/RemoteInterface.java | 34 +++++++++++++++++++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 08eca27b5..bb0b69292 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -65,6 +65,17 @@ + + + + + + + = 0) { + builder.append('\\'); + } + builder.append(c); + } + builder.append('"'); + return builder.toString(); + } + private String openNewWindow(String iInitialCommand) { TermService service = mTermService; From 8f584cf60e9fc7244662a7674f49bf7c739d565a Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Thu, 24 Apr 2014 09:39:54 -0700 Subject: [PATCH 653/847] Avoid NPE due to null mAltBuffer mAltBuffer is set to null in finish(). --- .../androidterm/emulatorview/TerminalEmulator.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TerminalEmulator.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TerminalEmulator.java index 326602a7b..7aa594386 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TerminalEmulator.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TerminalEmulator.java @@ -448,7 +448,9 @@ public void updateSize(int columns, int rows) { // Try to resize the screen without getting the transcript int[] cursor = { mCursorCol, mCursorRow }; boolean fastResize = mMainBuffer.fastResize(columns, rows, cursor); - mAltBuffer.resize(columns, rows, getStyle()); + if (mAltBuffer != null) { + mAltBuffer.resize(columns, rows, getStyle()); + } GrowableIntArray cursorColor = null; String charAtCursor = null; @@ -872,7 +874,9 @@ private void doEscLSBQuest(byte b) { case 47: case 1047: case 1049: - mScreen = mAltBuffer; + if (mAltBuffer != null) { + mScreen = mAltBuffer; + } break; } if (arg >= 1000 && arg <= 1003) { @@ -1933,7 +1937,9 @@ public void setColorScheme(ColorScheme scheme) { mDefaultForeColor = TextStyle.ciForeground; mDefaultBackColor = TextStyle.ciBackground; mMainBuffer.setColorScheme(scheme); - mAltBuffer.setColorScheme(scheme); + if (mAltBuffer != null) { + mAltBuffer.setColorScheme(scheme); + } } public String getSelectedText(int x1, int y1, int x2, int y2) { From e7d53442046c1319591b88b45d857d1c5fe2e719 Mon Sep 17 00:00:00 2001 From: "Frank P. Westlake" Date: Sun, 27 Apr 2014 08:08:23 -0700 Subject: [PATCH 654/847] Update RemoteInterface.java New Intent.data procedure for launching scripts. --- src/jackpal/androidterm/RemoteInterface.java | 37 ++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/src/jackpal/androidterm/RemoteInterface.java b/src/jackpal/androidterm/RemoteInterface.java index b1cf1faaf..82449b7a5 100644 --- a/src/jackpal/androidterm/RemoteInterface.java +++ b/src/jackpal/androidterm/RemoteInterface.java @@ -35,6 +35,13 @@ import jackpal.androidterm.util.SessionList; import jackpal.androidterm.util.TermSettings; +/* + * New procedure for launching a command in ATE. + * Build the path and arguments into a Uri and set that into Intent.data. + * intent.data(new Uri.Builder().setScheme("file").setPath(path).setFragment(arguments)) + * + * The old procedure of using Intent.Extra is still available but is discouraged. + */ public class RemoteInterface extends Activity { private static final String ACTION_RUN_SCRIPT = "jackpal.androidterm.RUN_SCRIPT"; @@ -89,12 +96,38 @@ private void handleIntent() { /* Someone with the appropriate permissions has asked us to run a script */ String handle = myIntent.getStringExtra(EXTRA_WINDOW_HANDLE); + String command=null; + /* + * First look in Intent.data for the path; if not there, revert to + * the EXTRA_INITIAL_COMMAND location. + */ + Uri uri=myIntent.getData(); + if(uri!=null) + { + String s=uri.getScheme(); + if(s!=null && s.toLowerCase().equals("file")) + { + command=uri.getPath(); + if(command!=null) + { + // Append any arguments. + if(null!=(s=uri.getFragment())) command+=" "+s; + } + } + } + if(command==null) command=myIntent.getStringExtra(EXTRA_INITIAL_COMMAND); + if(command==null) + { + // The calling application failed to provide a script path but apparently wants a terminal, + // so open a terminal without an initial command. + command=""; + } if (handle != null) { // Target the request at an existing window if open - handle = appendToWindow(handle, myIntent.getStringExtra(EXTRA_INITIAL_COMMAND)); + handle = appendToWindow(handle, command); } else { // Open a new window - handle = openNewWindow(myIntent.getStringExtra(EXTRA_INITIAL_COMMAND)); + handle = openNewWindow(command); } Intent result = new Intent(); result.putExtra(EXTRA_WINDOW_HANDLE, handle); From 58fe2402f7739d0865dbb4085efbab33c35f71cc Mon Sep 17 00:00:00 2001 From: FrankWestlake Date: Mon, 28 Apr 2014 07:44:09 -0700 Subject: [PATCH 655/847] RemoteInterface.java with new Intent.data procedure. --- src/jackpal/androidterm/RemoteInterface.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/jackpal/androidterm/RemoteInterface.java b/src/jackpal/androidterm/RemoteInterface.java index 82449b7a5..86fc5ee3b 100644 --- a/src/jackpal/androidterm/RemoteInterface.java +++ b/src/jackpal/androidterm/RemoteInterface.java @@ -110,6 +110,7 @@ private void handleIntent() { command=uri.getPath(); if(command!=null) { + command=quoteForBash(command); // Append any arguments. if(null!=(s=uri.getFragment())) command+=" "+s; } From c91ef263eda0a66dfd9b777efe5544905bad0356 Mon Sep 17 00:00:00 2001 From: FrankWestlake Date: Mon, 28 Apr 2014 09:01:56 -0700 Subject: [PATCH 656/847] First implementation of shortcuts --- AndroidManifest.xml | 28 ++ lint.xml | 3 + res/drawable/ic_folder.png | Bin 0 -> 1163 bytes res/drawable/ic_folderup.png | Bin 0 -> 1066 bytes res/values/id.xml | 4 + .../androidterm/shortcuts/AddShortcut.java | 155 +++++++ .../androidterm/shortcuts/FSNavigator.java | 399 ++++++++++++++++++ .../androidterm/shortcuts/GetInput.java | 83 ++++ .../androidterm/shortcuts/TextIcon.java | 40 ++ 9 files changed, 712 insertions(+) create mode 100644 lint.xml create mode 100644 res/drawable/ic_folder.png create mode 100644 res/drawable/ic_folderup.png create mode 100644 res/values/id.xml create mode 100755 src/jackpal/androidterm/shortcuts/AddShortcut.java create mode 100755 src/jackpal/androidterm/shortcuts/FSNavigator.java create mode 100755 src/jackpal/androidterm/shortcuts/GetInput.java create mode 100755 src/jackpal/androidterm/shortcuts/TextIcon.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index bb0b69292..d2f4dec49 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -81,6 +81,34 @@ + + + + + + + + + + + + + + + + + diff --git a/lint.xml b/lint.xml new file mode 100644 index 000000000..ee0eead5b --- /dev/null +++ b/lint.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/res/drawable/ic_folder.png b/res/drawable/ic_folder.png new file mode 100644 index 0000000000000000000000000000000000000000..13016013f5de7dd68814e65207fde47c7c280d8f GIT binary patch literal 1163 zcmV;61a$j}P)(E9HId+iWrUNupk;16clAuR$>q@f+7(-#8cF(xd;KhcrXYaJoynt zlq`4@6+C#+9Svy-L2yk#Og1Ys`!U&>*`Dcsul&DP^?D}RWVZ#^T+&R@-S73h`qjVc zRkxw5e3)whmkylzyms42l|HLh>CsE){+#3YCl01><8lGk-w=R5j@265?|I?ot#>>} z#62Rn0_zfGi`1C;g1-6whRf-s%cj$WWx%^|IF&uhKF!5_TtJ@s$O?(fJfeeSpZVciCJmdIpX zaz1babFjQm6@WT_ZF4defEzbhy#gCCbo$&T+Vy<;67GKR*<2f7{8c!G^Y&R-ld@KW zI-MoTIHMJ7DYXs``CrSOX4Jp`K>9Q;e}r`|4{!_sV|yuUo}pIrJoQJm(M|WgMx)p7 zfYX$!qg$bl(|^VgVMfoyG9T&(O1ldX6RPL)^y}A0G5{RHLtnwpToz#F%NlQ@>*$7C zCurO5{Z!q&9gAgJ54jqPBg3*pIK`!$JmY%c=#sH5V!MbMTA&l(ev&`%N_qqjeF{6d z3gFby+TjXS-rTY0b=q?Kq|CAv0H5OsK{+S2 z8*2mn_VH%k`NVrPy5%m3BrI$-Y6ZYTSXsw=;wW#x>EAFED&P!&8Ni0}03OFqOgGmD z*!}F(Kw<@8%23!5!aH3Mr#@Q9D+nsd1Ch_h$`63*bzkXJyJpsWB)>hmQ4_I;qkPAI(8KIhpiw*D+2+TlKq`-Ou- z0VFGoi+ere*<^a51i;>RMg5Qfhp2oTJ_mK@LhemW3)_%m@5rbM92}_)9C>dPe*c>JJ524O`yCf0M33sQ{Vf*3wQ3k=Q&Uh zd>y<4)iU*kPx~;FDw~U2V)jdUtcI0~H4*C+sY_~Ykq*mn0EvyU97wskM40+rNMV7ulM z&gK_jN+1MSUu{qFc@KF9i!F%DSZE?)z&>ExSP4#lG;<>K3idtxDmxC&rvz0d0A6Cd zk8Kd8Hb|I+`dP;&ga%X%2ul!`Ahs}HL*5%J%T)jh*YQJ-zs2;9`x1o`D1rjE`{}Db zEcU!~!}9W9k75My5)m=1VP}qE1f+l| zU`M_i0bnT2fg|~Z48#t28`Qx<9d#E_c^RWZ3(pJ<49On~h%quLEoykGl1WV{Z({}u zZKxJg9S?OWop~}-8xja2Hr6LYnafjum;`|NYWkpBgxEqxuJvRr!!aU~BgFv|Bl9RG zX5=?C;V1*Kjm0Gh&9uX5BYVpYN4Yvuh5;KRfd1&rYZ^E00GpOk^$e*FEG9?K5{^Z9-=!htiu*ZmXGwE(sQcjf!d k2m!0WU;Q)T?YJHP07-mUaX=gz+5i9m07*qoM6N<$f~o1-WB>pF literal 0 HcmV?d00001 diff --git a/res/values/id.xml b/res/values/id.xml new file mode 100644 index 000000000..b07728184 --- /dev/null +++ b/res/values/id.xml @@ -0,0 +1,4 @@ + + + + diff --git a/src/jackpal/androidterm/shortcuts/AddShortcut.java b/src/jackpal/androidterm/shortcuts/AddShortcut.java new file mode 100755 index 000000000..9c71c5f42 --- /dev/null +++ b/src/jackpal/androidterm/shortcuts/AddShortcut.java @@ -0,0 +1,155 @@ +//From the desk of Frank P. Westlake; public domain. +package jackpal.androidterm.shortcuts; + +import android.app. Activity; +import android.content. Context; +import android.content. DialogInterface; +import android.content. Intent; +import android.content. SharedPreferences; +import android.net. Uri; +import android.os. Bundle; +import android.os. Environment; +import android.preference. PreferenceManager; +import android.widget. EditText; + +import java.io. File; +import jackpal.androidterm.R; + +public class AddShortcut + extends Activity +{ + private final int OP_MAKE_SHORTCUT= 1; + private Context context= this; + private SharedPreferences SP; + private Intent intent; + private String pkg_jackpal= "jackpal.androidterm"; + + ////////////////////////////////////////////////////////////////////// + protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + SP=PreferenceManager.getDefaultSharedPreferences(context); + intent=getIntent(); + String action=intent.getAction(); + if(action!=null && action.equals("android.intent.action.CREATE_SHORTCUT")) makeShortcut(null); + else finish(); + } + ////////////////////////////////////////////////////////////////////// + void makeShortcut(final String path) + { + if(path==null) + { + String last=SP.getString("lastPath", null); + File get=last==null?Environment.getExternalStorageDirectory():new File(last).getParentFile(); + startActivityForResult( + new Intent() + .setClass(getApplicationContext(), jackpal.androidterm.shortcuts.FSNavigator.class) + .setData(Uri.fromFile(get)) + .putExtra("title", "SELECT SCRIPT FILE") + , OP_MAKE_SHORTCUT + ); + } + else + { + String name=path.replaceAll(".*/", ""); + GetInput.get( + context + , OP_MAKE_SHORTCUT + , "ICON" + , new String[] + { + "Arguments", "" + , "Icon label; text appearing below the icon", name + , "Icon text; leave blank to use default icon", "" + , "Icon color", SP.getString("colorShortcut", "0xFFFFFFFF") + } + , "OK" + , null + , "CANCEL" + , new GetInput.GetInputCallback() + { + public void getInputCallback(int operation, int which, EditText[] edits) + { + switch(which) + { + case DialogInterface.BUTTON_POSITIVE: + switch(operation) + { + case OP_MAKE_SHORTCUT: buildShortcut(path, edits); break; + } + break; + case DialogInterface.BUTTON_NEUTRAL: + case DialogInterface.BUTTON_NEGATIVE: + finish(); + break; + } + } + } + ); + } + } + ////////////////////////////////////////////////////////////////////// + void buildShortcut(final String path, EditText inputs[]) + { + int ix=0; + int ARGS=ix++, NAME=ix++, TEXT=ix++, COLOR=ix++; + String shortcutName= inputs[NAME].getText().toString(); + String shortcutText= inputs[TEXT].getText().toString(); + String arguments= inputs[ARGS].getText().toString(); +android.net.Uri uri=new android.net.Uri.Builder() + .scheme("File") + .path(path) + .build(); + int shortcutColor=Long.decode(inputs[COLOR].getText().toString()).intValue(); + Intent target= new Intent().setClassName(pkg_jackpal, pkg_jackpal+".RemoteInterface"); + target.setAction(pkg_jackpal+".RUN_SCRIPT"); + target.setDataAndType(uri, "text/plain"); + target.putExtra(pkg_jackpal+".window_handle", shortcutName); + target.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + Intent wrapper= new Intent(); + wrapper.setAction("com.android.launcher.action.INSTALL_SHORTCUT"); + wrapper.putExtra(Intent.EXTRA_SHORTCUT_INTENT, target); + if(shortcutName!=null && !shortcutName.equals("")) + { + wrapper.putExtra(Intent.EXTRA_SHORTCUT_NAME, shortcutName); + } + if(shortcutText!=null && !shortcutText.equals("")) + { + wrapper.putExtra( + Intent.EXTRA_SHORTCUT_ICON + , TextIcon.getTextIcon( + shortcutText + , shortcutColor + , 96 + , 96 + ) + ); + } + else + { + wrapper.putExtra( + Intent.EXTRA_SHORTCUT_ICON_RESOURCE + , Intent.ShortcutIconResource.fromContext(context, R.drawable.ic_launcher) + ); + } + setResult(RESULT_OK, wrapper); + finish(); + } + ////////////////////////////////////////////////////////////////////// + protected void onActivityResult(int requestCode, int resultCode, Intent data) + { + super.onActivityResult(requestCode, resultCode, data); + Uri uri= null; + String path= null; + switch(requestCode) + { + case OP_MAKE_SHORTCUT: + if(data!=null && (uri=data.getData())!=null && (path=uri.getPath())!=null) + { + makeShortcut(path);//No null! + } + else finish(); + break; + } + } +} diff --git a/src/jackpal/androidterm/shortcuts/FSNavigator.java b/src/jackpal/androidterm/shortcuts/FSNavigator.java new file mode 100755 index 000000000..b5150d59e --- /dev/null +++ b/src/jackpal/androidterm/shortcuts/FSNavigator.java @@ -0,0 +1,399 @@ +//From the desk of Frank P. Westlake; public domain. +package jackpal.androidterm.shortcuts; + +import android.graphics. Typeface; +import android.net. Uri; +import android.os.Environment; +import android.view. Gravity; +import android.view. KeyEvent; +import android.view. View; +import android.content. SharedPreferences; +import android.preference. PreferenceManager; +import android.widget. HorizontalScrollView; +import android.widget. ImageView; +import android.widget. LinearLayout; +import android.widget. ScrollView; +import android.widget. EditText; +import android.widget. TextView; +import java.io. File; +import java.io. IOException; +import android.content. Intent; +import jackpal.androidterm. R; + +public class FSNavigator + extends android.app.Activity +{ + final int BUTTON_SIZE= 150; + final int VIEW_ID_LL= 0; + final int VIEW_ID_TV= 1; + final int VIEW_ID_HIGH= VIEW_ID_TV; + final int COLOR_LIGHT= 0xFFAAAAAA; + final int COLOR_DARK= 0xFF000000; + String colorScheme= COLOR_LIGHT+" "+COLOR_DARK; + int color_text= COLOR_DARK; + int color_back= COLOR_LIGHT; + private android.content.Context context= this; + private File cd= null; + float textLg= 24; + File zipDir= null; + private boolean allowFileEntry= false; + private boolean allowPathEntry= true; + public SharedPreferences SP= null; +boolean setColors=false;//true; + + //////////////////////////////////////////////////////////// + public void onCreate(android.os.Bundle savedInstanceState) + { + setTitle("File Selector"); + super.onCreate(savedInstanceState); + getWindow().setSoftInputMode(android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); + SP= PreferenceManager.getDefaultSharedPreferences(context); + + Intent intent= getIntent(); + if(null==(chdir(intent.getData().getPath()))) chdir(Environment.getRootDirectory()); + if(intent.hasExtra("title")) setTitle(intent.getStringExtra("title")); + if(intent.hasExtra("allowFileEntry")) allowFileEntry=intent.getBooleanExtra("allowFileEntry", allowFileEntry); + if(intent.hasExtra("allowPathEntry")) allowPathEntry=intent.getBooleanExtra("allowFileEntry", allowPathEntry); + if(intent.hasExtra("colorScheme")) colorScheme=intent.getStringExtra("colorScheme"); + setColorScheme(); + makeView(); + } + //////////////////////////////////////////////////////////// + void setColorScheme() + { + colorScheme=SP.getString("colorScheme", colorScheme); + String ss[]=colorScheme.split("\\s+"); + color_back=Integer.decode(ss[0]); + color_text=Integer.decode(ss[1]); + } + //////////////////////////////////////////////////////////// + private File chdir(File file) + { + String path=getCanonicalPath(file); + System.setProperty("user.dir", path); + return(cd=new File(path)); + } + private File chdir(String path) + { + return(chdir(new File(path))); + } + //////////////////////////////////////////////////////////// + private TextView entryDividerH() + { + LinearLayout.LayoutParams btn_params=new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.FILL_PARENT + , 1 + , 1 + ); + TextView b1=new TextView(context); + if(setColors) b1.setBackgroundColor(color_text); + b1.setLayoutParams(btn_params); + return(b1); + } + //////////////////////////////////////////////////////////// + View.OnClickListener fileListener=new View.OnClickListener() + { + public void onClick(View view) + { + setResult(RESULT_OK, getIntent().setData(Uri.fromFile(new File(cd, (String)view.getTag(R.id.tag_filename))))); + finish(); + } + }; + //////////////////////////////////////////////////////////// + public View fileEntry(final String entry) + { + boolean newFile=entry==null && (allowFileEntry || allowPathEntry); +// LAYOUT + LinearLayout ll=new LinearLayout(context); + //ll.setBackgroundColor(colorBackground); + ll.setLayoutParams( + new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.FILL_PARENT + , LinearLayout.LayoutParams.WRAP_CONTENT + , 1 + ) + ); + ll.setOrientation(LinearLayout.HORIZONTAL); + ll.setGravity(android.view.Gravity.FILL); + if(setColors) ll.setBackgroundColor(color_back); + ll.setId(0); +// FILENAME + final TextView tv; + if(newFile) + { + tv=new EditText(context); + if(allowPathEntry) tv.setHint("Write in a path to go to."); + else if(allowFileEntry) tv.setHint("Enter new file name here."); +// tv.setSingleLine(); + if(setColors) tv.setTextColor(color_back); + if(setColors) tv.setBackgroundColor(color_text); + tv.setLayoutParams( + new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.FILL_PARENT + , LinearLayout.LayoutParams.FILL_PARENT + , 2 + ) + ); + tv.setOnKeyListener( + new EditText.OnKeyListener() + { + public boolean onKey(View v, int keyCode, KeyEvent event) + { + if(keyCode==KeyEvent.KEYCODE_ENTER) + { + String path=tv.getText().toString(); + File file=new File(getCanonicalPath(path)); + chdir(file.getParentFile()==null?file:file.getParentFile()); + if(file.isFile()) + { + setResult(RESULT_OK, getIntent().setData(Uri.fromFile(file))); + finish(); + } + else + { + chdir(file); + makeView(); + } + return(true); + } + return(false); + } + } + ); + ll.addView(tv); + } + else + { + tv=new TextView(context); + tv.setText(entry); + tv.setClickable(true); + tv.setLongClickable(true); + tv.setOnClickListener(fileListener); + if(setColors) tv.setTextColor(color_text); + tv.setLayoutParams( + new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.FILL_PARENT + , LinearLayout.LayoutParams.FILL_PARENT + , 1 + ) + ); + HorizontalScrollView hv=new HorizontalScrollView(context); + hv.setFillViewport(true); + hv.setLayoutParams( + new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.FILL_PARENT + , BUTTON_SIZE//LinearLayout.LayoutParams.WRAP_CONTENT + , 7 + ) + ); + hv.addView(tv); + ll.addView(hv); + } + tv.setSingleLine(); +// tv.setMaxLines(1); + tv.setTextSize(textLg); +// tv.setTypeface(Typeface.DEFAULT, Typeface.BOLD); + tv.setTypeface(Typeface.SERIF, Typeface.BOLD); + tv.setTag(R.id.tag_filename, entry); + tv.setGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT); + tv.setPadding(10, 5, 10, 5); + tv.setId(1); + + return(ll); + } + //////////////////////////////////////////////////////////// + private ImageView entryFolder(String name) + { + LinearLayout.LayoutParams btn_params=new LinearLayout.LayoutParams( + 120//BUTTON_SIZE//LinearLayout.LayoutParams.WRAP_CONTENT + , 120//BUTTON_SIZE//LinearLayout.LayoutParams.MATCH_PARENT + , 1 + ); + ImageView b1=new ImageView(context); + b1.setClickable(true); + // b1.setLongClickable(true); + b1.setLayoutParams(btn_params); + b1.setImageResource(name.equals("..")?R.drawable.ic_folderup:R.drawable.ic_folder); + b1.setOnClickListener(directoryListener); + b1.setTag(R.id.tag_filename, name); + b1.setScaleType(ImageView.ScaleType.CENTER_INSIDE); +// b1.setMaxHeight(10); +// b1.setMaxWidth(10); + return(b1); + } + //////////////////////////////////////////////////////////// + View.OnClickListener directoryListener=new View.OnClickListener() + { + public void onClick(View view) + { + File file=new File((String)view.getTag(R.id.tag_filename)); + if(file.isFile()) + { + setResult(RESULT_OK, getIntent().setData(Uri.fromFile(file))); + finish(); + } + else chdir(file); + //else chdir((String)view.getTag(R.id.tag_filename)); + makeView(); + } + }; + //////////////////////////////////////////////////////////// + public View directoryEntry(final String name) + { +// LAYOUT + LinearLayout ll=new LinearLayout(context); + //ll.setBackgroundColor(colorBackground); + ll.setLayoutParams( + new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.FILL_PARENT + , BUTTON_SIZE//LinearLayout.LayoutParams.WRAP_CONTENT + , 2 + ) + ); + ll.setOrientation(LinearLayout.HORIZONTAL); + ll.setGravity(android.view.Gravity.FILL); + if(setColors) ll.setBackgroundColor(color_back); + ll.setId(0); + //ll.setTag(R.id.tag_filename, name); + ll.setOnClickListener(directoryListener); +// FILENAME + TextView tv=new TextView(context); +// tv.setText(name+"\n"); + //tv.setText(name.equals("..")?cd.getPath():name); + tv.setText(name.equals("..")?"["+cd.getPath()+"]":name);//"↖" + tv.setClickable(true); + tv.setLongClickable(true); + tv.setTag(R.id.tag_filename, name); + tv.setOnClickListener(directoryListener); + tv.setMaxLines(1); + if(setColors) tv.setTextColor(color_text); + tv.setTextSize(textLg); +// tv.setTag(entry.getName()); + tv.setGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT); + tv.setPadding(10, 5, 10, 5); + tv.setLayoutParams( + new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.FILL_PARENT + , BUTTON_SIZE//LinearLayout.LayoutParams.MATCH_PARENT + , 1 + ) + ); + HorizontalScrollView hv=new HorizontalScrollView(context); + hv.addView(tv); +// hv.setTag(R.id.tag_filename, name); + hv.setFillViewport(true); + hv.setOnClickListener(directoryListener); + hv.setLayoutParams( + new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.FILL_PARENT + , BUTTON_SIZE//LinearLayout.LayoutParams.WRAP_CONTENT + , 7 + ) + ); + ImageView b1=entryFolder(name);//U+2681, U+2687, U+2689 + ll.addView(b1); + ll.addView(hv); + + return(ll); + } + //////////////////////////////////////////////////////////// + public boolean onKeyUp(int keyCode, KeyEvent event) + { + if((keyCode == KeyEvent.KEYCODE_BACK)) {finish(); return(true);} + else return(super.onKeyUp(keyCode, event)); + } + //////////////////////////////////////////////////////////// + public void makeView() + { + if(cd == null) chdir("/"); + String path=getCanonicalPath(cd); + //chdir(path); + //setTitle(path); + final LinearLayout bg=new LinearLayout(context); + if(setColors) bg.setBackgroundColor(color_back); + bg.setLayoutParams( + new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.FILL_PARENT + , LinearLayout.LayoutParams.WRAP_CONTENT + , 1 + ) + ); + bg.setOrientation(LinearLayout.VERTICAL); + bg.setGravity(android.view.Gravity.FILL); + // ll.addView(makeActionBarView()); + + + final LinearLayout ll=new LinearLayout(context); + if(setColors) ll.setBackgroundColor(color_back); + ll.setLayoutParams( + new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.FILL_PARENT + , LinearLayout.LayoutParams.WRAP_CONTENT + , 1 + ) + ); + ll.setOrientation(LinearLayout.VERTICAL); + ll.setGravity(android.view.Gravity.FILL); + // ll.addView(makeActionBarView()); + final ScrollView sv=new ScrollView(context); + if(setColors) ll.setBackgroundColor(color_back); + //sv.setFillViewport(true); + sv.setLayoutParams( + new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.FILL_PARENT + , LinearLayout.LayoutParams.WRAP_CONTENT + , 1 + ) + ); + //sv.setBackgroundColor(colorBackground); + if(path.equals("")) {chdir(path="/");} + if(!path.equals("/")) + { + bg.addView(directoryEntry(".."), android.view.ViewGroup.LayoutParams.MATCH_PARENT, android.view.ViewGroup.LayoutParams.WRAP_CONTENT); + //bg.addView(entryDividerH(), android.view.ViewGroup.LayoutParams.MATCH_PARENT, android.view.ViewGroup.LayoutParams.WRAP_CONTENT); + //bg.addView(entryDividerH()); + } + sv.addView(ll); + bg.addView(sv); + + String zd[]=cd.list(new java.io.FilenameFilter(){public boolean accept(File file, String name){return( new File(file, name).isDirectory() );}}); + if(zd!=null) + { + java.util.Arrays.sort(zd, 0, zd.length, stringSortComparator); + for(int i=0, n=zd.length; i stringSortComparator=new java.util.Comparator() + { + public int compare(String a, String b) {return(a.toLowerCase().compareTo(b.toLowerCase()));} + }; + ////////////////////////////////////////////////////////////////////// + String getCanonicalPath(String path) + { + return(getCanonicalPath(new File(path))); + } + String getCanonicalPath(File file) + { + try{return(file.getCanonicalPath());}catch(IOException e){return(file.getPath());} + } + ////////////////////////////////////////////////////////////////////// +} diff --git a/src/jackpal/androidterm/shortcuts/GetInput.java b/src/jackpal/androidterm/shortcuts/GetInput.java new file mode 100755 index 000000000..09ddb7c43 --- /dev/null +++ b/src/jackpal/androidterm/shortcuts/GetInput.java @@ -0,0 +1,83 @@ +//From the desk of Frank P. Westlake; public domain. +package jackpal.androidterm.shortcuts; + +import android.app. AlertDialog; +import android.content. Context; +import android.content. DialogInterface; +import android.text. InputType; +import android.widget. EditText; +import android.widget. LinearLayout; +import android.widget. TextView; + +////////////////////////////////////////////////////////////////////// +public class GetInput +{ + public GetInput(){} + static public void get( + final Context context + // operation=0 + , final String title + , final String strings[] + , final String sButton1 + , final String sButton2 + , final String sButton3 + , final GetInputCallback inputCallback + ) + { + get(context, 0, title, strings, sButton1, sButton2, sButton3, inputCallback); + } + //////////////////////////////////////////////////////////// + static public void get( + final Context context + , final int operation + , final String title + , final String strings[] + , final String sButton1 + , final String sButton2 + , final String sButton3 + , final GetInputCallback inputCallback + ) + { + final AlertDialog.Builder alert=new AlertDialog.Builder(context); + LinearLayout ll=new LinearLayout(context); + ll.setOrientation(LinearLayout.VERTICAL); + int size=strings==null?0:strings.length/2; + final EditText edits[]=new EditText[size]; + if(strings!=null) + { + for(int i=0, e=0, n=strings.length; i Date: Mon, 28 Apr 2014 13:47:07 -0700 Subject: [PATCH 657/847] FSNavigator: pause/resume, last directory. AndroidManifest: rename activity. --- AndroidManifest.xml | 2 +- .../androidterm/shortcuts/FSNavigator.java | 32 +++++++++++++++++-- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index d2f4dec49..c0cdee393 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -82,7 +82,7 @@ android:label="@string/window_list" /> Date: Mon, 28 Apr 2014 16:00:04 -0700 Subject: [PATCH 658/847] AddShortcut.jav --- src/jackpal/androidterm/shortcuts/AddShortcut.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/jackpal/androidterm/shortcuts/AddShortcut.java b/src/jackpal/androidterm/shortcuts/AddShortcut.java index 9c71c5f42..9d7d70c7b 100755 --- a/src/jackpal/androidterm/shortcuts/AddShortcut.java +++ b/src/jackpal/androidterm/shortcuts/AddShortcut.java @@ -99,6 +99,7 @@ void buildShortcut(final String path, EditText inputs[]) android.net.Uri uri=new android.net.Uri.Builder() .scheme("File") .path(path) + .fragment(arguments) .build(); int shortcutColor=Long.decode(inputs[COLOR].getText().toString()).intValue(); Intent target= new Intent().setClassName(pkg_jackpal, pkg_jackpal+".RemoteInterface"); From d2a6e79ed71232b6fab6454c6b79c5b079ebc581 Mon Sep 17 00:00:00 2001 From: FrankWestlake Date: Wed, 30 Apr 2014 11:21:01 -0700 Subject: [PATCH 659/847] Shortcuts --- AndroidManifest.xml | 82 +++++++------- pushnote | 8 ++ .../androidterm/shortcuts/AddShortcut.java | 101 ++++++++++++------ .../androidterm/shortcuts/GetInput.java | 83 -------------- 4 files changed, 116 insertions(+), 158 deletions(-) create mode 100755 pushnote delete mode 100755 src/jackpal/androidterm/shortcuts/GetInput.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index c0cdee393..9bcf5a19a 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,3 +1,4 @@ + - - - - - - + android:exported="true" + > + + + + + + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + +
diff --git a/pushnote b/pushnote new file mode 100755 index 000000000..7cb9bc241 --- /dev/null +++ b/pushnote @@ -0,0 +1,8 @@ +#!/bin/bash +work="/home/frank/a/Android-Terminal-Emulator" +note="/note3/storage/extSdCard/java/a/Android-Terminal-Emulator" + +chown -R frank:programmer "$work" + +mirror "$work/*" "$note" +tonote3 "$note" diff --git a/src/jackpal/androidterm/shortcuts/AddShortcut.java b/src/jackpal/androidterm/shortcuts/AddShortcut.java index 9d7d70c7b..5a38922d9 100755 --- a/src/jackpal/androidterm/shortcuts/AddShortcut.java +++ b/src/jackpal/androidterm/shortcuts/AddShortcut.java @@ -2,6 +2,7 @@ package jackpal.androidterm.shortcuts; import android.app. Activity; +import android.app.AlertDialog; import android.content. Context; import android.content. DialogInterface; import android.content. Intent; @@ -11,6 +12,9 @@ import android.os. Environment; import android.preference. PreferenceManager; import android.widget. EditText; +import android.widget.LinearLayout; +import android.widget.ScrollView; +import android.widget.TextView; import java.io. File; import jackpal.androidterm.R; @@ -52,43 +56,68 @@ void makeShortcut(final String path) else { String name=path.replaceAll(".*/", ""); - GetInput.get( - context - , OP_MAKE_SHORTCUT - , "ICON" - , new String[] - { - "Arguments", "" - , "Icon label; text appearing below the icon", name - , "Icon text; leave blank to use default icon", "" - , "Icon color", SP.getString("colorShortcut", "0xFFFFFFFF") - } - , "OK" - , null - , "CANCEL" - , new GetInput.GetInputCallback() + final AlertDialog.Builder alert=new AlertDialog.Builder(context); + LinearLayout lv=new LinearLayout(context); + lv.setOrientation(LinearLayout.VERTICAL); + final EditText et[]=new EditText[4]; + for(int i=0, n=et.length; i Date: Wed, 30 Apr 2014 15:03:53 -0700 Subject: [PATCH 660/847] Shortcut work --- .../androidterm/shortcuts/AddShortcut.java | 31 ++++++++++++++++--- .../androidterm/shortcuts/FSNavigator.java | 2 ++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/jackpal/androidterm/shortcuts/AddShortcut.java b/src/jackpal/androidterm/shortcuts/AddShortcut.java index 5a38922d9..f5de9e268 100755 --- a/src/jackpal/androidterm/shortcuts/AddShortcut.java +++ b/src/jackpal/androidterm/shortcuts/AddShortcut.java @@ -81,7 +81,7 @@ void makeShortcut(final String path) sv.addView(lv); alert.setView(sv); - alert.setTitle("ICON DATA"); + alert.setTitle(name); alert.setPositiveButton( android.R.string.yes , new DialogInterface.OnClickListener() @@ -93,7 +93,7 @@ public void onClick(DialogInterface dialog, int which) } ); alert.setNegativeButton( - android.R.string.no + android.R.string.cancel , new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) @@ -128,11 +128,34 @@ void buildShortcut(final String path, EditText inputs[]) android.net.Uri uri=new android.net.Uri.Builder() .scheme("File") .path(path) - .fragment(arguments) + .fragment(arguments!=null?arguments:"") .build(); int shortcutColor=0xFFFFFFFF; String s=inputs[COLOR].getText().toString(); - if(s!=null && !s.equals("")) shortcutColor=Long.decode(s).intValue(); + if(s!=null && !s.equals("")) try + { + shortcutColor=Long.decode(s).intValue(); + } + catch(NumberFormatException nfe) + { + AlertDialog.Builder ad=new AlertDialog.Builder(context)//, AlertDialog.THEME_DEVICE_DEFAULT_LIGHT) + .setPositiveButton( + android.R.string.yes + , new DialogInterface.OnClickListener() + { + public void onClick(DialogInterface dialog, int which) + { + makeShortcut(path); + } + } + ) + .setTitle(android.R.string.dialog_alert_title) + .setMessage( + nfe.getLocalizedMessage() + ); + ad.show(); + return; + } Intent target= new Intent().setClassName(pkg_jackpal, pkg_jackpal+".RemoteInterface"); target.setAction(pkg_jackpal+".RUN_SCRIPT"); target.setDataAndType(uri, "text/plain"); diff --git a/src/jackpal/androidterm/shortcuts/FSNavigator.java b/src/jackpal/androidterm/shortcuts/FSNavigator.java index a7ee24415..a2705e556 100755 --- a/src/jackpal/androidterm/shortcuts/FSNavigator.java +++ b/src/jackpal/androidterm/shortcuts/FSNavigator.java @@ -54,6 +54,8 @@ public class FSNavigator public void onCreate(android.os.Bundle savedInstanceState) { setTitle("File Selector"); + setTheme(android.R.style.Theme); +// setTheme(android.R.style.Theme_Light); super.onCreate(savedInstanceState); getWindow().setSoftInputMode(android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); SP=getSharedPreferences("shortcuts", Context.MODE_PRIVATE); From dd11820fe1985caf8a4bea3a33afbae2ca7011b5 Mon Sep 17 00:00:00 2001 From: Arnd Strube Date: Thu, 1 May 2014 23:41:42 +0200 Subject: [PATCH 661/847] Add READ_EXTERNAL_STORAGE permission. --- AndroidManifest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index bb0b69292..d76b16e0e 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -6,6 +6,7 @@ + From e1a29c721fab28ba927afe1be82e541aa344256e Mon Sep 17 00:00:00 2001 From: FrankWestlake Date: Fri, 2 May 2014 08:09:24 -0700 Subject: [PATCH 662/847] Added theme choice to FSNavigator. --- .../androidterm/shortcuts/FSNavigator.java | 70 +++++++++++++++---- 1 file changed, 58 insertions(+), 12 deletions(-) diff --git a/src/jackpal/androidterm/shortcuts/FSNavigator.java b/src/jackpal/androidterm/shortcuts/FSNavigator.java index a2705e556..356406a49 100755 --- a/src/jackpal/androidterm/shortcuts/FSNavigator.java +++ b/src/jackpal/androidterm/shortcuts/FSNavigator.java @@ -15,6 +15,8 @@ import android.os.Environment; import android.view. Gravity; import android.view. KeyEvent; +import android.view.Menu; +import android.view.MenuItem; import android.view. View; import android.content.Context; import android.content. SharedPreferences; @@ -28,10 +30,12 @@ import java.io. IOException; import android.content. Intent; import jackpal.androidterm. R; +import jackpal.androidterm.compat.AndroidCompat; public class FSNavigator extends android.app.Activity { + public final int ACTION_THEME_SWAP= 0x00000100; final int BUTTON_SIZE= 150; final int VIEW_ID_LL= 0; final int VIEW_ID_TV= 1; @@ -48,17 +52,32 @@ public class FSNavigator private boolean allowFileEntry= false; private boolean allowPathEntry= true; public SharedPreferences SP= null; -boolean setColors=false;//true; + private int theme= android.R.style.Theme_Light; + boolean setColors=false;//true; + private int build_version= android.os.Build.VERSION.SDK_INT; + //////////////////////////////////////////////////////////// + private void swapTheme() + { + switch(theme) + { + case android.R.style.Theme_Light: theme=android.R.style.Theme; break; + case android.R.style.Theme: theme=android.R.style.Theme_Light; break; + default: return; + } + SP.edit().putInt("theme", theme).commit(); + startActivityForResult(getIntent().addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT), -1); + finish(); + } //////////////////////////////////////////////////////////// public void onCreate(android.os.Bundle savedInstanceState) { - setTitle("File Selector"); - setTheme(android.R.style.Theme); -// setTheme(android.R.style.Theme_Light); super.onCreate(savedInstanceState); - getWindow().setSoftInputMode(android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); + setTitle("File Selector"); SP=getSharedPreferences("shortcuts", Context.MODE_PRIVATE); + theme=SP.getInt("theme", theme); + setTheme(theme); + getWindow().setSoftInputMode(android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); Intent intent= getIntent(); if(null==(chdir(intent.getData().getPath()))) chdir(Environment.getRootDirectory()); @@ -284,13 +303,19 @@ public View directoryEntry(final String name) ll.setGravity(android.view.Gravity.FILL); if(setColors) ll.setBackgroundColor(color_back); ll.setId(0); - //ll.setTag(R.id.tag_filename, name); ll.setOnClickListener(directoryListener); // FILENAME TextView tv=new TextView(context); -// tv.setText(name+"\n"); - //tv.setText(name.equals("..")?cd.getPath():name); - tv.setText(name.equals("..")?"["+cd.getPath()+"]":name);//"↖" + if(name.equals("..")) + { + tv.setText("["+cd.getPath()+"]"); + tv.setGravity(Gravity.CENTER); + } + else + { + tv.setText(name); + tv.setGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT); + } tv.setClickable(true); tv.setLongClickable(true); tv.setTag(R.id.tag_filename, name); @@ -298,8 +323,6 @@ public View directoryEntry(final String name) tv.setMaxLines(1); if(setColors) tv.setTextColor(color_text); tv.setTextSize(textLg); -// tv.setTag(entry.getName()); - tv.setGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT); tv.setPadding(10, 5, 10, 5); tv.setLayoutParams( new LinearLayout.LayoutParams( @@ -310,7 +333,6 @@ public View directoryEntry(final String name) ); HorizontalScrollView hv=new HorizontalScrollView(context); hv.addView(tv); -// hv.setTag(R.id.tag_filename, name); hv.setFillViewport(true); hv.setOnClickListener(directoryListener); hv.setLayoutParams( @@ -426,4 +448,28 @@ String getCanonicalPath(File file) try{return(file.getCanonicalPath());}catch(IOException e){return(file.getPath());} } ////////////////////////////////////////////////////////////////////// + public boolean onCreateOptionsMenu(Menu menu) +// public boolean onPrepareOptionsMenu(Menu menu) + { + super.onCreateOptionsMenu(menu); +// super.onPrepareOptionsMenu(menu); menu.clear(); + menu.add(0, ACTION_THEME_SWAP, 0, "Change theme"); + return(true); + } + ////////////////////////////////////////////////////////////////////// + public boolean onOptionsItemSelected(MenuItem item) + { + super.onOptionsItemSelected(item); + return(doOptionsItem(item.getItemId())); + } + ////////////////////////////////////////////////////////////////////// + public boolean doOptionsItem(int itemId) + { + switch(itemId) + { + case ACTION_THEME_SWAP: swapTheme(); return(true); + } + return(false); + } + ////////////////////////////////////////////////////////////////////// } From 34bfcffb850bfb5649ccc5b7a1fe3a499b4ad237 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Thu, 1 May 2014 17:52:41 -0700 Subject: [PATCH 663/847] TranscriptScreen: increase mTotalRows when resizing if needed Currently, when you attempt to increase the number of screen rows in a TranscriptScreen beyond the value of mTotalRows supplied when the TranscriptScreen was initially created, the backing store allocated in init() will not be large enough to hold the contents of the screen. This results in strange behavior, such as duplicate lines or lines overwriting each other in full-screen applications. Fix this by bumping mTotalRows as needed when resizing. --- .../jackpal/androidterm/emulatorview/TranscriptScreen.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TranscriptScreen.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TranscriptScreen.java index 60fca45dd..8c4430850 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TranscriptScreen.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TranscriptScreen.java @@ -420,6 +420,10 @@ public boolean fastResize(int columns, int rows, int[] cursor) { } public void resize(int columns, int rows, int style) { + // Ensure backing store will be large enough to hold the whole screen + if (rows > mTotalRows) { + mTotalRows = rows; + } init(columns, mTotalRows, rows, style); } From dc881b30c0c272350092da34a14698e9082e9306 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Thu, 1 May 2014 00:57:24 -0700 Subject: [PATCH 664/847] TerminalEmulator: fix screen resizing when alternate buffer is in use A full resize of the screen while the alternate buffer is current (e.g. if GNU screen or another fullscreen terminal application is running) can currently crash with the following stack trace: java.lang.IllegalArgumentException at jackpal.androidterm.emulatorview.UnicodeTranscript.setChar(UnicodeTranscript.java:727) at jackpal.androidterm.emulatorview.UnicodeTranscript.setChar(UnicodeTranscript.java:714) at jackpal.androidterm.emulatorview.TranscriptScreen.set(TranscriptScreen.java:99) at jackpal.androidterm.emulatorview.TerminalEmulator.updateSize(TerminalEmulator.java:464) [...] This happens because TerminalEmulator is trying to mark the position of the cursor in the alternate buffer, but the alternate buffer was already resized. There's no guarantee that the current location of the cursor is still a valid position on the resized screen, and if it isn't, the exception above is thrown. Even when the terminal doesn't crash, resizing the screen causes the screen to blank briefly until the application redraws the screen, since the contents of the alternate buffer are discarded during the resize. To fix this, we note that when resizing the screen, we don't actually need to care whether the main or alternate buffer is current -- we just want to resize the current buffer (whichever one that is). Therefore, instead of treating the main and alternate buffers differently during resizing, we just work with the active buffer. In principle, we could defer the resizing of the inactive buffer until it becomes current, but this introduces the possibility of latency at some undetermined future time (when the buffers are swapped). It's probably better to do the work during the resize event itself, when the user is more willing to tolerate a pause and there's usually an animation to help disguise it. (The alternate buffer has no scrollback, so even a full resize will be relatively quick; therefore, the only case where this could become noticeable is when the alternate buffer is current, but the main buffer has a very long scrollback.) We therefore resize the inactive buffer immediately, taking some care to avoid problems with cursor positioning (in particular, we can't trim blank lines, because without the cursor position marking, we can't be sure whether they're significant or not). --- .../emulatorview/TerminalEmulator.java | 72 ++++++++++++++++--- .../emulatorview/UnicodeTranscript.java | 11 ++- 2 files changed, 70 insertions(+), 13 deletions(-) diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TerminalEmulator.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TerminalEmulator.java index 7aa594386..e97a0afe6 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TerminalEmulator.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TerminalEmulator.java @@ -445,12 +445,17 @@ public void updateSize(int columns, int rows) { throw new IllegalArgumentException("rows:" + rows); } + TranscriptScreen screen = mScreen; + TranscriptScreen altScreen; + if (screen != mMainBuffer) { + altScreen = mMainBuffer; + } else { + altScreen = mAltBuffer; + } + // Try to resize the screen without getting the transcript int[] cursor = { mCursorCol, mCursorRow }; - boolean fastResize = mMainBuffer.fastResize(columns, rows, cursor); - if (mAltBuffer != null) { - mAltBuffer.resize(columns, rows, getStyle()); - } + boolean fastResize = screen.fastResize(columns, rows, cursor); GrowableIntArray cursorColor = null; String charAtCursor = null; @@ -462,13 +467,25 @@ public void updateSize(int columns, int rows) { * This is an epic hack that lets us restore the cursor later... */ cursorColor = new GrowableIntArray(1); - charAtCursor = mScreen.getSelectedText(cursorColor, mCursorCol, mCursorRow, mCursorCol, mCursorRow); - mScreen.set(mCursorCol, mCursorRow, 27, 0); + charAtCursor = screen.getSelectedText(cursorColor, mCursorCol, mCursorRow, mCursorCol, mCursorRow); + screen.set(mCursorCol, mCursorRow, 27, 0); colors = new GrowableIntArray(1024); - transcriptText = mMainBuffer.getTranscriptText(colors); + transcriptText = screen.getTranscriptText(colors); + screen.resize(columns, rows, getStyle()); + } + + boolean altFastResize = true; + GrowableIntArray altColors = null; + String altTranscriptText = null; + if (altScreen != null) { + altFastResize = altScreen.fastResize(columns, rows, null); - mMainBuffer.resize(columns, rows, getStyle()); + if (!altFastResize) { + altColors = new GrowableIntArray(1024); + altTranscriptText = altScreen.getTranscriptText(altColors); + altScreen.resize(columns, rows, getStyle()); + } } if (mRows != rows) { @@ -485,6 +502,41 @@ public void updateSize(int columns, int rows) { System.arraycopy(oldTabStop, 0, mTabStop, 0, toTransfer); } + if (!altFastResize) { + boolean wasAboutToAutoWrap = mAboutToAutoWrap; + + // Restore the contents of the inactive screen's buffer + mScreen = altScreen; + mCursorRow = 0; + mCursorCol = 0; + mAboutToAutoWrap = false; + + int end = altTranscriptText.length()-1; + /* Unlike for the main transcript below, don't trim off trailing + * newlines -- the alternate transcript lacks a cursor marking, so + * we might introduce an unwanted vertical shift in the screen + * contents this way */ + char c, cLow; + int colorOffset = 0; + for (int i = 0; i <= end; i++) { + c = altTranscriptText.charAt(i); + int style = altColors.at(i-colorOffset); + if (Character.isHighSurrogate(c)) { + cLow = altTranscriptText.charAt(++i); + emit(Character.toCodePoint(c, cLow), style); + ++colorOffset; + } else if (c == '\n') { + setCursorCol(0); + doLinefeed(); + } else { + emit(c, style); + } + } + + mScreen = screen; + mAboutToAutoWrap = wasAboutToAutoWrap; + } + if (fastResize) { // Only need to make sure the cursor is in the right spot if (cursor[0] >= 0 && cursor[1] >= 0) { @@ -527,7 +579,7 @@ public void updateSize(int columns, int rows) { is the place to restore the cursor to */ newCursorRow = mCursorRow; newCursorCol = mCursorCol; - newCursorTranscriptPos = mMainBuffer.getActiveRows(); + newCursorTranscriptPos = screen.getActiveRows(); if (charAtCursor != null && charAtCursor.length() > 0) { // Emit the real character that was in this spot int encodedCursorColor = cursorColor.at(0); @@ -545,7 +597,7 @@ public void updateSize(int columns, int rows) { /* Adjust for any scrolling between the time we marked the cursor location and now */ - int scrollCount = mMainBuffer.getActiveRows() - newCursorTranscriptPos; + int scrollCount = screen.getActiveRows() - newCursorTranscriptPos; if (scrollCount > 0 && scrollCount <= newCursorRow) { mCursorRow -= scrollCount; } else if (scrollCount > newCursorRow) { diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/UnicodeTranscript.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/UnicodeTranscript.java index 825440381..926df38fc 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/UnicodeTranscript.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/UnicodeTranscript.java @@ -134,7 +134,10 @@ public boolean getLineWrap(int row) { * * @param newColumns The number of columns the screen should have. * @param newRows The number of rows the screen should have. - * @param cursor An int[2] containing the cursor location. + * @param cursor An int[2] containing the current cursor location; if the + * resize succeeds, this will be updated with the new cursor + * location. If null, don't do cursor-position-dependent tasks such + * as trimming blank lines during the resize. * @return Whether or not the resize succeeded. If the resize failed, * the caller may "resize" the screen by copying out all the data * and placing it into a new transcript of the correct size. @@ -161,7 +164,7 @@ public boolean resize(int newColumns, int newRows, int[] cursor) { lineWrap[index] = false; } shift = -activeTranscriptRows; - } else if (shift > 0 && cursor[1] != screenRows - 1) { + } else if (shift > 0 && cursor != null && cursor[1] != screenRows - 1) { /* When shrinking the screen, we want to hide blank lines at the bottom in preference to lines at the top of the screen */ Object[] lines = mLines; @@ -225,7 +228,9 @@ public boolean resize(int newColumns, int newRows, int[] cursor) { } else { mActiveTranscriptRows += shift; } - cursor[1] -= shift; + if (cursor != null) { + cursor[1] -= shift; + } mScreenRows = newRows; return true; From 59a9060efe2bb12c274b10c86d4eb5b481dbb848 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Tue, 6 May 2014 21:54:20 -0700 Subject: [PATCH 665/847] Auto-linkification only match URLs that start with https?: Fixes #319 --- .../emulatorview/EmulatorView.java | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/EmulatorView.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/EmulatorView.java index 8c5129a17..167ac49c7 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/EmulatorView.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/EmulatorView.java @@ -34,9 +34,11 @@ import android.text.TextUtils; import android.text.style.URLSpan; import android.text.util.Linkify; +import android.text.util.Linkify.MatchFilter; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; +import android.util.Patterns; import android.view.GestureDetector; import android.view.KeyEvent; import android.view.MotionEvent; @@ -227,6 +229,33 @@ public void run() { */ private Hashtable mLinkLayer = new Hashtable(); + /** + * Accept links that start with http[s]: + */ + private static class HttpMatchFilter implements MatchFilter { + public boolean acceptMatch(CharSequence s, int start, int end) { + return startsWith(s, start, end, "http:") || + startsWith(s, start, end, "https:"); + } + + private boolean startsWith(CharSequence s, int start, int end, + String prefix) { + int prefixLen = prefix.length(); + int fragmentLen = end - start; + if (prefixLen > fragmentLen) { + return false; + } + for (int i = 0; i < prefixLen; i++) { + if (s.charAt(start + i) != prefix.charAt(i)) { + return false; + } + } + return true; + } + } + + private static MatchFilter sHttpMatchFilter = new HttpMatchFilter(); + /** * Convert any URLs in the current row into a URLSpan, * and store that result in a hash table of URLSpan entries. @@ -265,7 +294,8 @@ private int createLinks(int row) ++lineCount; } - Linkify.addLinks(textToLinkify, Linkify.WEB_URLS); + Linkify.addLinks(textToLinkify, Patterns.WEB_URL, + null, sHttpMatchFilter, null); URLSpan [] urls = textToLinkify.getSpans(0, textToLinkify.length(), URLSpan.class); if(urls.length > 0) { From 29318c6f2b84259e7797447f7bdcf2a276e0be94 Mon Sep 17 00:00:00 2001 From: FrankWestlake Date: Thu, 8 May 2014 08:17:54 -0700 Subject: [PATCH 666/847] Shortcuts pahse 2. --- src/jackpal/androidterm/RemoteInterface.java | 24 +- .../androidterm/shortcuts/AddShortcut.java | 320 ++++++------ .../androidterm/shortcuts/ColorValue.java | 194 +++++++ .../androidterm/shortcuts/FSNavigator.java | 189 ++++--- .../androidterm/shortcuts/FSNavigator.java.bk | 474 ++++++++++++++++++ 5 files changed, 975 insertions(+), 226 deletions(-) create mode 100755 src/jackpal/androidterm/shortcuts/ColorValue.java create mode 100755 src/jackpal/androidterm/shortcuts/FSNavigator.java.bk diff --git a/src/jackpal/androidterm/RemoteInterface.java b/src/jackpal/androidterm/RemoteInterface.java index 86fc5ee3b..d35bf9b45 100644 --- a/src/jackpal/androidterm/RemoteInterface.java +++ b/src/jackpal/androidterm/RemoteInterface.java @@ -102,27 +102,24 @@ private void handleIntent() { * the EXTRA_INITIAL_COMMAND location. */ Uri uri=myIntent.getData(); - if(uri!=null) + if(uri!=null) // scheme[path][arguments] { String s=uri.getScheme(); if(s!=null && s.toLowerCase().equals("file")) { command=uri.getPath(); - if(command!=null) - { - command=quoteForBash(command); - // Append any arguments. - if(null!=(s=uri.getFragment())) command+=" "+s; - } + // Allow for the command to be contained within the arguments string. + if(command==null) command=""; + if(!command.equals("")) command=quoteForBash(command); + // Append any arguments. + if(null!=(s=uri.getFragment())) command+=" "+s; } } + // If Intent.data not used then fall back to old method. if(command==null) command=myIntent.getStringExtra(EXTRA_INITIAL_COMMAND); - if(command==null) - { - // The calling application failed to provide a script path but apparently wants a terminal, - // so open a terminal without an initial command. - command=""; - } + // If still null the calling application failed to provide a script path but + // apparently wants a terminal, so open a terminal without an initial command. + if(command==null) command=""; if (handle != null) { // Target the request at an existing window if open handle = appendToWindow(handle, command); @@ -136,6 +133,7 @@ private void handleIntent() { } else if (action.equals(Intent.ACTION_SEND) && myIntent.hasExtra(Intent.EXTRA_STREAM)) { + /* "permission.RUN_SCRIPT" not required as this is merely opening a new window. */ Object extraStream = myIntent.getExtras().get(Intent.EXTRA_STREAM); if (extraStream instanceof Uri) { String path = ((Uri) extraStream).getPath(); diff --git a/src/jackpal/androidterm/shortcuts/AddShortcut.java b/src/jackpal/androidterm/shortcuts/AddShortcut.java index f5de9e268..7967e087d 100755 --- a/src/jackpal/androidterm/shortcuts/AddShortcut.java +++ b/src/jackpal/androidterm/shortcuts/AddShortcut.java @@ -2,31 +2,42 @@ package jackpal.androidterm.shortcuts; import android.app. Activity; -import android.app.AlertDialog; +import android.app. AlertDialog; import android.content. Context; import android.content. DialogInterface; import android.content. Intent; import android.content. SharedPreferences; +import android.graphics. Typeface; import android.net. Uri; import android.os. Bundle; import android.os. Environment; import android.preference. PreferenceManager; +import android.view. Gravity; +import android.view. View; +import android.widget. Button; +import android.widget. ImageView; +import android.widget. LinearLayout; +import android.widget. ScrollView; +import android.widget. TextView; import android.widget. EditText; -import android.widget.LinearLayout; -import android.widget.ScrollView; -import android.widget.TextView; - import java.io. File; -import jackpal.androidterm.R; public class AddShortcut extends Activity { - private final int OP_MAKE_SHORTCUT= 1; - private Context context= this; - private SharedPreferences SP; - private Intent intent; - private String pkg_jackpal= "jackpal.androidterm"; + private final int OP_MAKE_SHORTCUT= 1; + private Intent intent; + private Context context= this; + private SharedPreferences SP; + private String pkg_jackpal= "jackpal.androidterm"; +// private int build_version= android.os.Build.VERSION.SDK_INT; + private int ix=0; + private int PATH=ix++, ARGS=ix++, NAME=ix++;//, TEXT=ix++, COLOR=ix++; + private final EditText et[]=new EditText[5]; + private String path; + private String name=""; +// private String lastPath=""; + private String iconText[]= {"", null}; ////////////////////////////////////////////////////////////////////// protected void onCreate(Bundle savedInstanceState) @@ -35,176 +46,205 @@ protected void onCreate(Bundle savedInstanceState) SP=PreferenceManager.getDefaultSharedPreferences(context); intent=getIntent(); String action=intent.getAction(); - if(action!=null && action.equals("android.intent.action.CREATE_SHORTCUT")) makeShortcut(null); + if(action!=null && action.equals("android.intent.action.CREATE_SHORTCUT")) makeShortcut(); else finish(); } ////////////////////////////////////////////////////////////////////// - void makeShortcut(final String path) +// public void onResume() +// { +// super.onResume(); +// if(path==null) +// { +// intent=getIntent(); +// String action=intent.getAction(); +// if(action!=null && action.equals("android.intent.action.CREATE_SHORTCUT")) makeShortcut(); +// else finish(); +// } +// } + ////////////////////////////////////////////////////////////////////// + void makeShortcut() { - if(path==null) - { - String last=SP.getString("lastPath", null); - File get=last==null?Environment.getExternalStorageDirectory():new File(last).getParentFile(); - startActivityForResult( - new Intent() - .setClass(getApplicationContext(), jackpal.androidterm.shortcuts.FSNavigator.class) - .setData(Uri.fromFile(get)) - .putExtra("title", "SELECT SCRIPT FILE") - , OP_MAKE_SHORTCUT - ); - } - else - { - String name=path.replaceAll(".*/", ""); - final AlertDialog.Builder alert=new AlertDialog.Builder(context); - LinearLayout lv=new LinearLayout(context); - lv.setOrientation(LinearLayout.VERTICAL); - final EditText et[]=new EditText[4]; - for(int i=0, n=et.length; i=14) builder=new AlertDialog.Builder(context, AlertDialog.THEME_DEVICE_DEFAULT_DARK); +// else if(build_version>=11) builder=new AlertDialog.Builder(context, AlertDialog.THEME_TRADITIONAL); +// else builder=new AlertDialog.Builder(context); + builder=new AlertDialog.Builder(context); + LinearLayout lv=new LinearLayout(context); + lv.setOrientation(LinearLayout.VERTICAL); + String lab[]={"α ", "R ", "G ", "B "}; + int clr[]={0xFFFFFFFF, 0xFFFF0000, 0xFF00FF00, 0xFF0000FF}; + for(int i=0, n=(Integer)imgview.getTag(); i<4; i++) color[i]=(n>>(24-i*8))&0xFF; + lt=new TextView(context); + lt.setText("LOCK"); + lt.setPadding(lt.getPaddingLeft(), lt.getPaddingTop(), 5, lt.getPaddingBottom()); + lt.setGravity(Gravity.RIGHT); + for(int i=0, n=value.length; i stringSortComparator=new java.util.Comparator() + { + public int compare(String a, String b) {return(a.toLowerCase().compareTo(b.toLowerCase()));} + }; + ////////////////////////////////////////////////////////////////////// + String getCanonicalPath(String path) + { + return(getCanonicalPath(new File(path))); + } + String getCanonicalPath(File file) + { + try{return(file.getCanonicalPath());}catch(IOException e){return(file.getPath());} + } + ////////////////////////////////////////////////////////////////////// + public boolean onCreateOptionsMenu(Menu menu) +// public boolean onPrepareOptionsMenu(Menu menu) + { + super.onCreateOptionsMenu(menu); +// super.onPrepareOptionsMenu(menu); menu.clear(); + menu.add(0, ACTION_THEME_SWAP, 0, "Change theme"); + return(true); + } + ////////////////////////////////////////////////////////////////////// + public boolean onOptionsItemSelected(MenuItem item) + { + super.onOptionsItemSelected(item); + return(doOptionsItem(item.getItemId())); + } + ////////////////////////////////////////////////////////////////////// + public boolean doOptionsItem(int itemId) + { + switch(itemId) + { + case ACTION_THEME_SWAP: swapTheme(); return(true); + } + return(false); + } + ////////////////////////////////////////////////////////////////////// +} From 126530b9b421fab5b32da792cfd9821d600aa2d4 Mon Sep 17 00:00:00 2001 From: FrankWestlake Date: Thu, 8 May 2014 08:34:25 -0700 Subject: [PATCH 667/847] Correct icon mixup. --- src/jackpal/androidterm/shortcuts/FSNavigator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jackpal/androidterm/shortcuts/FSNavigator.java b/src/jackpal/androidterm/shortcuts/FSNavigator.java index 62981758e..1da52f383 100755 --- a/src/jackpal/androidterm/shortcuts/FSNavigator.java +++ b/src/jackpal/androidterm/shortcuts/FSNavigator.java @@ -290,7 +290,7 @@ private ImageView entryFolder(String name) b1.setClickable(true); // b1.setLongClickable(true); b1.setLayoutParams(btn_params); - b1.setImageResource(name.equals("..")?R.drawable.ic_folder:R.drawable.ic_folderup); + b1.setImageResource(name.equals("..")?R.drawable.ic_folderup:R.drawable.ic_folder); b1.setOnClickListener(directoryListener); b1.setTag(R.id.tag_filename, name); b1.setScaleType(ImageView.ScaleType.CENTER_INSIDE); From a5965b2cc4ee798059c58807a770a1aa764c79e4 Mon Sep 17 00:00:00 2001 From: FrankWestlake Date: Fri, 9 May 2014 03:39:36 -0700 Subject: [PATCH 668/847] Shortcut changes --- .../androidterm/shortcuts/AddShortcut.java | 29 +- .../androidterm/shortcuts/FSNavigator.java.bk | 474 ------------------ 2 files changed, 20 insertions(+), 483 deletions(-) delete mode 100755 src/jackpal/androidterm/shortcuts/FSNavigator.java.bk diff --git a/src/jackpal/androidterm/shortcuts/AddShortcut.java b/src/jackpal/androidterm/shortcuts/AddShortcut.java index 7967e087d..455e3215e 100755 --- a/src/jackpal/androidterm/shortcuts/AddShortcut.java +++ b/src/jackpal/androidterm/shortcuts/AddShortcut.java @@ -95,6 +95,13 @@ public void onClick(View p1) } } ); + lv.addView( + layoutTextViewH( + "Command window requires full path, no arguments. For other commands use Arguments window (ex: cd /sdcard)." + , null + , false + ) + ); lv.addView(layoutViewViewH(btn_path, et[PATH])); lv.addView(layoutTextViewH("Arguments:", et[ARGS])); lv.addView(layoutTextViewH("Shortcut label:", et[NAME])); @@ -154,17 +161,21 @@ public void onClick(DialogInterface dialog, int which) } ////////////////////////////////////////////////////////////////////// LinearLayout layoutTextViewH(String text, View vw) + { + return(layoutTextViewH(text, vw, true)); + } + LinearLayout layoutTextViewH(String text, View vw, boolean attributes) { LinearLayout.LayoutParams lp=new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT, 1); - TextView tv=new TextView(context); - tv.setText(text); - tv.setTypeface(Typeface.DEFAULT_BOLD); - tv.setGravity(Gravity.RIGHT|Gravity.CENTER_VERTICAL); - tv.setPadding(10, tv.getPaddingTop(), 10, tv.getPaddingBottom()); - LinearLayout lh=new LinearLayout(context); - lh.setOrientation(LinearLayout.HORIZONTAL); - lh.addView(tv, lp); - if(vw!=null) lh.addView(vw, lp); + TextView tv=new TextView(context); + tv.setText(text); + if(attributes) tv.setTypeface(Typeface.DEFAULT_BOLD); + if(attributes) tv.setGravity(Gravity.RIGHT|Gravity.CENTER_VERTICAL); + tv.setPadding(10, tv.getPaddingTop(), 10, tv.getPaddingBottom()); + LinearLayout lh=new LinearLayout(context); + lh.setOrientation(LinearLayout.HORIZONTAL); + lh.addView(tv, lp); + if(vw!=null) lh.addView(vw, lp); return(lh); } ////////////////////////////////////////////////////////////////////// diff --git a/src/jackpal/androidterm/shortcuts/FSNavigator.java.bk b/src/jackpal/androidterm/shortcuts/FSNavigator.java.bk deleted file mode 100755 index 77870513a..000000000 --- a/src/jackpal/androidterm/shortcuts/FSNavigator.java.bk +++ /dev/null @@ -1,474 +0,0 @@ -//From the desk of Frank P. Westlake; public domain. -package jackpal.androidterm.shortcuts; -/* - * This is from another program I wrote and is still being modified for this one, - * so some of the variables will be changed, removed, or replaced by constants. - * - * A menu will be added to allow color theme change, font size change, root allow/disallow, - * and other features. I like this navigator more than others I've used so I hope to make it - * easily copied to other applications, perhaps also as a separate application. - * - */ - -import android.graphics. Typeface; -import android.net. Uri; -import android.os.Environment; -import android.view. Gravity; -import android.view. KeyEvent; -import android.view.Menu; -import android.view.MenuItem; -import android.view. View; -import android.content.Context; -import android.content. SharedPreferences; -import android.widget. HorizontalScrollView; -import android.widget. ImageView; -import android.widget. LinearLayout; -import android.widget. ScrollView; -import android.widget. EditText; -import android.widget. TextView; -import java.io. File; -import java.io. IOException; -import android.content. Intent; -import jackpal.androidterm. R; -import jackpal.androidterm.compat.AndroidCompat; - -public class FSNavigator - extends android.app.Activity -{ - public final int ACTION_THEME_SWAP= 0x00000100; - final int BUTTON_SIZE= 150; - final int VIEW_ID_LL= 0; - final int VIEW_ID_TV= 1; - final int VIEW_ID_HIGH= VIEW_ID_TV; - final int COLOR_LIGHT= 0xFFAAAAAA; - final int COLOR_DARK= 0xFF000000; - String colorScheme= COLOR_LIGHT+" "+COLOR_DARK; - int color_text= COLOR_DARK; - int color_back= COLOR_LIGHT; - private android.content.Context context= this; - private File cd= null; - float textLg= 24; - File zipDir= null; - private boolean allowFileEntry= false; - private boolean allowPathEntry= true; - public SharedPreferences SP= null; - private int theme= android.R.style.Theme_Light; - boolean setColors=false;//true; - private int build_version= android.os.Build.VERSION.SDK_INT; - - //////////////////////////////////////////////////////////// - private void swapTheme() - { - switch(theme) - { - case android.R.style.Theme_Light: theme=android.R.style.Theme; break; - case android.R.style.Theme: theme=android.R.style.Theme_Light; break; - default: return; - } - SP.edit().putInt("theme", theme).commit(); - startActivityForResult(getIntent().addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT), -1); - finish(); - } - //////////////////////////////////////////////////////////// - public void onCreate(android.os.Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - setTitle("File Selector"); - SP=getSharedPreferences("shortcuts", Context.MODE_PRIVATE); - theme=SP.getInt("theme", theme); - setTheme(theme); - getWindow().setSoftInputMode(android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); - - Intent intent= getIntent(); - if(null==(chdir(intent.getData().getPath()))) chdir(Environment.getExternalStorageDirectory()); - if(intent.hasExtra("title")) setTitle(intent.getStringExtra("title")); - if(intent.hasExtra("allowFileEntry")) allowFileEntry=intent.getBooleanExtra("allowFileEntry", allowFileEntry); - if(intent.hasExtra("allowPathEntry")) allowPathEntry=intent.getBooleanExtra("allowFileEntry", allowPathEntry); - if(intent.hasExtra("colorScheme")) colorScheme=intent.getStringExtra("colorScheme"); - setColorScheme(); -// makeView(); - } - //////////////////////////////////////////////////////////// - public void onPause() - { - super.onPause(); - SP.edit().putString("lastDirectory", getCanonicalPath(cd)).commit(); - } - //////////////////////////////////////////////////////////// - public void onResume() - { - super.onResume(); - String lastDirectory=SP.getString("lastDirectory", null); - if(lastDirectory!=null) chdir(lastDirectory); - makeView(); - } - //////////////////////////////////////////////////////////// - void setColorScheme() - { - colorScheme=SP.getString("colorScheme", colorScheme); - String ss[]=colorScheme.split("\\s+"); - color_back=Integer.decode(ss[0]); - color_text=Integer.decode(ss[1]); - } - //////////////////////////////////////////////////////////// - private File chdir(File file) - { - String path=getCanonicalPath(file); - System.setProperty("user.dir", path); - return(cd=new File(path)); - } - private File chdir(String path) - { - return(chdir(new File(path))); - } - //////////////////////////////////////////////////////////// - private TextView entryDividerH() - { - LinearLayout.LayoutParams btn_params=new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.FILL_PARENT - , 1 - , 1 - ); - TextView b1=new TextView(context); - if(setColors) b1.setBackgroundColor(color_text); - b1.setLayoutParams(btn_params); - return(b1); - } - //////////////////////////////////////////////////////////// - View.OnClickListener fileListener=new View.OnClickListener() - { - public void onClick(View view) - { - setResult(RESULT_OK, getIntent().setData(Uri.fromFile(new File(cd, (String)view.getTag(R.id.tag_filename))))); - finish(); - } - }; - //////////////////////////////////////////////////////////// - public View fileEntry(final String entry) - { - boolean newFile=entry==null && (allowFileEntry || allowPathEntry); -// LAYOUT - LinearLayout ll=new LinearLayout(context); - //ll.setBackgroundColor(colorBackground); - ll.setLayoutParams( - new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.FILL_PARENT - , LinearLayout.LayoutParams.WRAP_CONTENT - , 1 - ) - ); - ll.setOrientation(LinearLayout.HORIZONTAL); - ll.setGravity(android.view.Gravity.FILL); - if(setColors) ll.setBackgroundColor(color_back); - ll.setId(0); -// FILENAME - final TextView tv; - if(newFile) - { - tv=new EditText(context); -/* - * A future option will be the curent path mirrored in the input window. - tv.setText(getCanonicalPath(cd)); tv.setSelectAllOnFocus(true); - */ - if(allowPathEntry) tv.setHint("Write in a path to go to."); - else if(allowFileEntry) tv.setHint("Enter new file name here."); -// tv.setSingleLine(); - if(setColors) tv.setTextColor(color_back); - if(setColors) tv.setBackgroundColor(color_text); - tv.setLayoutParams( - new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.FILL_PARENT - , LinearLayout.LayoutParams.FILL_PARENT - , 2 - ) - ); - tv.setOnKeyListener( - new EditText.OnKeyListener() - { - public boolean onKey(View v, int keyCode, KeyEvent event) - { - if(keyCode==KeyEvent.KEYCODE_ENTER) - { - String path=tv.getText().toString(); - File file=new File(getCanonicalPath(path)); - chdir(file.getParentFile()==null?file:file.getParentFile()); - if(file.isFile()) - { - setResult(RESULT_OK, getIntent().setData(Uri.fromFile(file))); - finish(); - } - else - { - chdir(file); - makeView(); - } - return(true); - } - return(false); - } - } - ); - ll.addView(tv); - } - else - { - tv=new TextView(context); - tv.setText(entry); - tv.setClickable(true); - tv.setLongClickable(true); - tv.setOnClickListener(fileListener); - if(setColors) tv.setTextColor(color_text); - tv.setLayoutParams( - new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.FILL_PARENT - , LinearLayout.LayoutParams.FILL_PARENT - , 1 - ) - ); - HorizontalScrollView hv=new HorizontalScrollView(context); - hv.setFillViewport(true); - hv.setLayoutParams( - new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.FILL_PARENT - , BUTTON_SIZE//LinearLayout.LayoutParams.WRAP_CONTENT - , 7 - ) - ); - hv.addView(tv); - ll.addView(hv); - } - tv.setSingleLine(); -// tv.setMaxLines(1); - tv.setTextSize(textLg); -// tv.setTypeface(Typeface.DEFAULT, Typeface.BOLD); - tv.setTypeface(Typeface.SERIF, Typeface.BOLD); - tv.setTag(R.id.tag_filename, entry); - tv.setGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT); - tv.setPadding(10, 5, 10, 5); - tv.setId(1); - - return(ll); - } - //////////////////////////////////////////////////////////// - private ImageView entryFolder(String name) - { - LinearLayout.LayoutParams btn_params=new LinearLayout.LayoutParams( - 120//BUTTON_SIZE//LinearLayout.LayoutParams.WRAP_CONTENT - , 120//BUTTON_SIZE//LinearLayout.LayoutParams.MATCH_PARENT - , 1 - ); - ImageView b1=new ImageView(context); - b1.setClickable(true); - // b1.setLongClickable(true); - b1.setLayoutParams(btn_params); - b1.setImageResource(name.equals("..")?R.drawable.ic_folderup:R.drawable.ic_folder); - b1.setOnClickListener(directoryListener); - b1.setTag(R.id.tag_filename, name); - b1.setScaleType(ImageView.ScaleType.CENTER_INSIDE); -// b1.setMaxHeight(10); -// b1.setMaxWidth(10); - return(b1); - } - //////////////////////////////////////////////////////////// - View.OnClickListener directoryListener=new View.OnClickListener() - { - public void onClick(View view) - { - File file=new File((String)view.getTag(R.id.tag_filename)); - if(file.isFile()) - { - setResult(RESULT_OK, getIntent().setData(Uri.fromFile(file))); - finish(); - } - else chdir(file); - //else chdir((String)view.getTag(R.id.tag_filename)); - makeView(); - } - }; - //////////////////////////////////////////////////////////// - public View directoryEntry(final String name) - { -// LAYOUT - LinearLayout ll=new LinearLayout(context); - //ll.setBackgroundColor(colorBackground); - ll.setLayoutParams( - new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.FILL_PARENT - , BUTTON_SIZE//LinearLayout.LayoutParams.WRAP_CONTENT - , 2 - ) - ); - ll.setOrientation(LinearLayout.HORIZONTAL); - ll.setGravity(android.view.Gravity.FILL); - if(setColors) ll.setBackgroundColor(color_back); - ll.setId(0); - ll.setOnClickListener(directoryListener); -// FILENAME - TextView tv=new TextView(context); - if(name.equals("..")) - { - tv.setText("["+cd.getPath()+"]"); - tv.setGravity(Gravity.CENTER); - } - else - { - tv.setText(name); - tv.setGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT); - } - tv.setClickable(true); - tv.setLongClickable(true); - tv.setTag(R.id.tag_filename, name); - tv.setOnClickListener(directoryListener); - tv.setMaxLines(1); - if(setColors) tv.setTextColor(color_text); - tv.setTextSize(textLg); - tv.setPadding(10, 5, 10, 5); - tv.setLayoutParams( - new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.FILL_PARENT - , BUTTON_SIZE//LinearLayout.LayoutParams.MATCH_PARENT - , 1 - ) - ); - HorizontalScrollView hv=new HorizontalScrollView(context); - hv.addView(tv); - hv.setFillViewport(true); - hv.setOnClickListener(directoryListener); - hv.setLayoutParams( - new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.FILL_PARENT - , BUTTON_SIZE//LinearLayout.LayoutParams.WRAP_CONTENT - , 7 - ) - ); - ImageView b1=entryFolder(name);//U+2681, U+2687, U+2689 - ll.addView(b1); - ll.addView(hv); - - return(ll); - } - //////////////////////////////////////////////////////////// - public boolean onKeyUp(int keyCode, KeyEvent event) - { - if((keyCode == KeyEvent.KEYCODE_BACK)) {finish(); return(true);} - else return(super.onKeyUp(keyCode, event)); - } - //////////////////////////////////////////////////////////// - public void makeView() - { - if(cd == null) chdir("/"); - String path=getCanonicalPath(cd); - //chdir(path); - //setTitle(path); - final LinearLayout bg=new LinearLayout(context); - if(setColors) bg.setBackgroundColor(color_back); - bg.setLayoutParams( - new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.FILL_PARENT - , LinearLayout.LayoutParams.WRAP_CONTENT - , 1 - ) - ); - bg.setOrientation(LinearLayout.VERTICAL); - bg.setGravity(android.view.Gravity.FILL); - // ll.addView(makeActionBarView()); - - - final LinearLayout ll=new LinearLayout(context); - if(setColors) ll.setBackgroundColor(color_back); - ll.setLayoutParams( - new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.FILL_PARENT - , LinearLayout.LayoutParams.WRAP_CONTENT - , 1 - ) - ); - ll.setOrientation(LinearLayout.VERTICAL); - ll.setGravity(android.view.Gravity.FILL); - // ll.addView(makeActionBarView()); - final ScrollView sv=new ScrollView(context); - if(setColors) ll.setBackgroundColor(color_back); - //sv.setFillViewport(true); - sv.setLayoutParams( - new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.FILL_PARENT - , LinearLayout.LayoutParams.WRAP_CONTENT - , 1 - ) - ); - //sv.setBackgroundColor(colorBackground); - if(path.equals("")) {chdir(path="/");} - if(!path.equals("/")) - { - bg.addView(directoryEntry(".."), android.view.ViewGroup.LayoutParams.MATCH_PARENT, android.view.ViewGroup.LayoutParams.WRAP_CONTENT); - //bg.addView(entryDividerH(), android.view.ViewGroup.LayoutParams.MATCH_PARENT, android.view.ViewGroup.LayoutParams.WRAP_CONTENT); - //bg.addView(entryDividerH()); - } - sv.addView(ll); - bg.addView(sv); - - String zd[]=cd.list(new java.io.FilenameFilter(){public boolean accept(File file, String name){return( new File(file, name).isDirectory() );}}); - if(zd!=null) - { - java.util.Arrays.sort(zd, 0, zd.length, stringSortComparator); - for(int i=0, n=zd.length; i stringSortComparator=new java.util.Comparator() - { - public int compare(String a, String b) {return(a.toLowerCase().compareTo(b.toLowerCase()));} - }; - ////////////////////////////////////////////////////////////////////// - String getCanonicalPath(String path) - { - return(getCanonicalPath(new File(path))); - } - String getCanonicalPath(File file) - { - try{return(file.getCanonicalPath());}catch(IOException e){return(file.getPath());} - } - ////////////////////////////////////////////////////////////////////// - public boolean onCreateOptionsMenu(Menu menu) -// public boolean onPrepareOptionsMenu(Menu menu) - { - super.onCreateOptionsMenu(menu); -// super.onPrepareOptionsMenu(menu); menu.clear(); - menu.add(0, ACTION_THEME_SWAP, 0, "Change theme"); - return(true); - } - ////////////////////////////////////////////////////////////////////// - public boolean onOptionsItemSelected(MenuItem item) - { - super.onOptionsItemSelected(item); - return(doOptionsItem(item.getItemId())); - } - ////////////////////////////////////////////////////////////////////// - public boolean doOptionsItem(int itemId) - { - switch(itemId) - { - case ACTION_THEME_SWAP: swapTheme(); return(true); - } - return(false); - } - ////////////////////////////////////////////////////////////////////// -} From 5df634d31672754c3e0ac9a58748ab5cdcb278c3 Mon Sep 17 00:00:00 2001 From: FrankWestlake Date: Fri, 9 May 2014 03:44:57 -0700 Subject: [PATCH 669/847] Case of null command already handled. --- src/jackpal/androidterm/RemoteInterface.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/jackpal/androidterm/RemoteInterface.java b/src/jackpal/androidterm/RemoteInterface.java index d35bf9b45..53e70587f 100644 --- a/src/jackpal/androidterm/RemoteInterface.java +++ b/src/jackpal/androidterm/RemoteInterface.java @@ -117,9 +117,6 @@ private void handleIntent() { } // If Intent.data not used then fall back to old method. if(command==null) command=myIntent.getStringExtra(EXTRA_INITIAL_COMMAND); - // If still null the calling application failed to provide a script path but - // apparently wants a terminal, so open a terminal without an initial command. - if(command==null) command=""; if (handle != null) { // Target the request at an existing window if open handle = appendToWindow(handle, command); From 22dcef075d124455945ddda79a8f6508154aedb6 Mon Sep 17 00:00:00 2001 From: FrankWestlake Date: Fri, 9 May 2014 03:45:20 -0700 Subject: [PATCH 670/847] Shortcut changes --- docs/shortcuts.txt | 0 .../androidterm/shortcuts/FSNavigator.java | 60 +++++++++---------- 2 files changed, 30 insertions(+), 30 deletions(-) create mode 100644 docs/shortcuts.txt diff --git a/docs/shortcuts.txt b/docs/shortcuts.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/jackpal/androidterm/shortcuts/FSNavigator.java b/src/jackpal/androidterm/shortcuts/FSNavigator.java index 1da52f383..16f9aedba 100755 --- a/src/jackpal/androidterm/shortcuts/FSNavigator.java +++ b/src/jackpal/androidterm/shortcuts/FSNavigator.java @@ -55,36 +55,6 @@ public class FSNavigator private File extSdCardFile; private String extSdCard; - //////////////////////////////////////////////////////////// - private void swapTheme() - { - switch(theme) - { - case android.R.style.Theme: theme=android.R.style.Theme_Light; break; - case android.R.style.Theme_Light: theme=android.R.style.Theme; break; - default: return; - } - SP.edit().putInt("theme", theme).commit(); - startActivityForResult(getIntent().addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT), -1); - finish(); - } - //////////////////////////////////////////////////////////// - private String ifAvailable(String goTo) - { - if(goTo.startsWith(extSdCard)) - { - String s=Environment.getExternalStorageState(); - if(s.equals(Environment.MEDIA_MOUNTED) - || s.equals(Environment.MEDIA_MOUNTED_READ_ONLY) - ) - { - return(goTo); - } - toast("External storage not available", 1); - return(extSdCard); - } - return(goTo); - } //////////////////////////////////////////////////////////// public void onCreate(android.os.Bundle savedInstanceState) { @@ -136,6 +106,36 @@ private void doResume() // super.onDestroy(); //} //////////////////////////////////////////////////////////// + private void swapTheme() + { + switch(theme) + { + case android.R.style.Theme: theme=android.R.style.Theme_Light; break; + case android.R.style.Theme_Light: theme=android.R.style.Theme; break; + default: return; + } + SP.edit().putInt("theme", theme).commit(); + startActivityForResult(getIntent().addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT), -1); + finish(); + } + //////////////////////////////////////////////////////////// + private String ifAvailable(String goTo) + { + if(goTo.startsWith(extSdCard)) + { + String s=Environment.getExternalStorageState(); + if(s.equals(Environment.MEDIA_MOUNTED) + || s.equals(Environment.MEDIA_MOUNTED_READ_ONLY) + ) + { + return(goTo); + } + toast("External storage not available", 1); + return(extSdCard); + } + return(goTo); + } + //////////////////////////////////////////////////////////// void setColorScheme() { colorScheme=SP.getString("colorScheme", colorScheme); From 383528cae983409867b245ec77b12887bb917c2d Mon Sep 17 00:00:00 2001 From: FrankWestlake Date: Fri, 9 May 2014 03:58:59 -0700 Subject: [PATCH 671/847] Shortcuts doc --- docs/shortcuts.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/shortcuts.txt b/docs/shortcuts.txt index e69de29bb..b50ae59ca 100644 --- a/docs/shortcuts.txt +++ b/docs/shortcuts.txt @@ -0,0 +1,6 @@ + I +A shortcut is a desktop icon which contains an Intent shich may contain +information for a specific application. + +Work in progress. + From 7e6074b09525806018952a47d09e2b4a83331f34 Mon Sep 17 00:00:00 2001 From: FrankWestlake Date: Fri, 9 May 2014 07:11:55 -0700 Subject: [PATCH 672/847] Add dialog text. --- src/jackpal/androidterm/shortcuts/AddShortcut.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/jackpal/androidterm/shortcuts/AddShortcut.java b/src/jackpal/androidterm/shortcuts/AddShortcut.java index 455e3215e..749cd5416 100755 --- a/src/jackpal/androidterm/shortcuts/AddShortcut.java +++ b/src/jackpal/androidterm/shortcuts/AddShortcut.java @@ -124,6 +124,13 @@ public void onClick(View p1) } } ); + lv.addView( + layoutTextViewH( + "Optionally create a text icon:" + , null + , false + ) + ); lv.addView(layoutViewViewH(btn_color, img)); final ScrollView sv=new ScrollView(context); sv.setFillViewport(true); From efcc9464bbd78d6862e230bddd0c21079f28c6b1 Mon Sep 17 00:00:00 2001 From: FrankWestlake Date: Fri, 9 May 2014 08:56:49 -0700 Subject: [PATCH 673/847] Shortcut auto-label --- docs/shortcuts.txt | 6 ------ .../androidterm/shortcuts/AddShortcut.java | 20 ++++++++++++++++++- 2 files changed, 19 insertions(+), 7 deletions(-) delete mode 100644 docs/shortcuts.txt diff --git a/docs/shortcuts.txt b/docs/shortcuts.txt deleted file mode 100644 index b50ae59ca..000000000 --- a/docs/shortcuts.txt +++ /dev/null @@ -1,6 +0,0 @@ - I -A shortcut is a desktop icon which contains an Intent shich may contain -information for a specific application. - -Work in progress. - diff --git a/src/jackpal/androidterm/shortcuts/AddShortcut.java b/src/jackpal/androidterm/shortcuts/AddShortcut.java index 749cd5416..c0179a719 100755 --- a/src/jackpal/androidterm/shortcuts/AddShortcut.java +++ b/src/jackpal/androidterm/shortcuts/AddShortcut.java @@ -14,6 +14,7 @@ import android.preference. PreferenceManager; import android.view. Gravity; import android.view. View; +import android.view. View.OnFocusChangeListener; import android.widget. Button; import android.widget. ImageView; import android.widget. LinearLayout; @@ -71,8 +72,25 @@ void makeShortcut() for(int i=0, n=et.length; i Date: Sat, 10 May 2014 07:24:34 -0700 Subject: [PATCH 674/847] Shortcut hex windows. --- .../androidterm/shortcuts/ColorValue.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/jackpal/androidterm/shortcuts/ColorValue.java b/src/jackpal/androidterm/shortcuts/ColorValue.java index 65de902d2..0f18a58d9 100755 --- a/src/jackpal/androidterm/shortcuts/ColorValue.java +++ b/src/jackpal/androidterm/shortcuts/ColorValue.java @@ -56,6 +56,7 @@ public void colorValue() // if(build_version>=14) builder=new AlertDialog.Builder(context, AlertDialog.THEME_DEVICE_DEFAULT_DARK); // else if(build_version>=11) builder=new AlertDialog.Builder(context, AlertDialog.THEME_TRADITIONAL); // else builder=new AlertDialog.Builder(context); + final TextView hexWindow[]=new TextView[4]; builder=new AlertDialog.Builder(context); LinearLayout lv=new LinearLayout(context); lv.setOrientation(LinearLayout.VERTICAL); @@ -116,6 +117,7 @@ private void doProgressChanged(SeekBar seekBar, int progress, boolean fromUser) if(i==me || (barLock && locks[i])) { color[i]=progress; +toHexWindow(hexWindow[i], color[i]); sb[i].setBackgroundColor(0xFF<<24|(progress<<(24-i*8))); sb[i].setProgress(progress); } @@ -141,6 +143,17 @@ public void onStopTrackingTouch(SeekBar seekBar) lh.addView(lk[i]); lv.addView(lh, FP, WC); } +{//Evaluating hex windows. + LinearLayout lh=new LinearLayout(context); + lh.setGravity(Gravity.CENTER); + for(int i=0; i<4; i++) + { + hexWindow[i]=new TextView(context); + toHexWindow(hexWindow[i], color[i]); + lh.addView(hexWindow[i]); + } + lv.addView(lh); +}//Evaluating hex windows. ScrollView sv=new ScrollView(context); sv.addView(lv); builder.setView(sv); @@ -157,6 +170,16 @@ public void onClick(DialogInterface dialog, int which) alert=builder.show(); started=true; } + ////////////////////////////////////////////////////////////////////// + public void toHexWindow(TextView tv, int k) + { + String HEX="0123456789ABCDEF"; + String s=""; + int n=8; + k&=(1L<<8)-1L; + for(n-=4; n>=0; n-=4) s+=HEX.charAt((k>>n)&0xF); + tv.setText(s); + } //////////////////////////////////////////////////////////// public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { From 738e219134fd190bc4483585aeed8743a808bc06 Mon Sep 17 00:00:00 2001 From: FrankWestlake Date: Sun, 11 May 2014 06:14:08 -0700 Subject: [PATCH 675/847] Shortcut code cleaned. --- .../androidterm/shortcuts/AddShortcut.java | 86 +++++++---------- .../androidterm/shortcuts/ColorValue.java | 87 ++++++++--------- .../androidterm/shortcuts/FSNavigator.java | 94 +++---------------- 3 files changed, 87 insertions(+), 180 deletions(-) diff --git a/src/jackpal/androidterm/shortcuts/AddShortcut.java b/src/jackpal/androidterm/shortcuts/AddShortcut.java index c0179a719..602f8044f 100755 --- a/src/jackpal/androidterm/shortcuts/AddShortcut.java +++ b/src/jackpal/androidterm/shortcuts/AddShortcut.java @@ -1,7 +1,6 @@ //From the desk of Frank P. Westlake; public domain. package jackpal.androidterm.shortcuts; -import android.app. Activity; import android.app. AlertDialog; import android.content. Context; import android.content. DialogInterface; @@ -24,45 +23,31 @@ import java.io. File; public class AddShortcut - extends Activity + extends android.app.Activity { private final int OP_MAKE_SHORTCUT= 1; - private Intent intent; - private Context context= this; - private SharedPreferences SP; - private String pkg_jackpal= "jackpal.androidterm"; -// private int build_version= android.os.Build.VERSION.SDK_INT; - private int ix=0; - private int PATH=ix++, ARGS=ix++, NAME=ix++;//, TEXT=ix++, COLOR=ix++; - private final EditText et[]=new EditText[5]; - private String path; - private String name=""; -// private String lastPath=""; - private String iconText[]= {"", null}; + private final Context context= this; + private SharedPreferences SP; + private final String pkg_jackpal= "jackpal.androidterm"; + private int ix= 0; + private final int PATH= ix++ + , ARGS= ix++ + , NAME= ix++; + private final EditText et[]= new EditText[5]; + private String path; + private String name=""; + private String iconText[]= {"", null}; ////////////////////////////////////////////////////////////////////// protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); SP=PreferenceManager.getDefaultSharedPreferences(context); - intent=getIntent(); - String action=intent.getAction(); + String action=getIntent().getAction(); if(action!=null && action.equals("android.intent.action.CREATE_SHORTCUT")) makeShortcut(); else finish(); } ////////////////////////////////////////////////////////////////////// -// public void onResume() -// { -// super.onResume(); -// if(path==null) -// { -// intent=getIntent(); -// String action=intent.getAction(); -// if(action!=null && action.equals("android.intent.action.CREATE_SHORTCUT")) makeShortcut(); -// else finish(); -// } -// } - ////////////////////////////////////////////////////////////////////// void makeShortcut() { if(path==null) path=""; @@ -92,27 +77,27 @@ public void onFocusChange(View view, boolean focus) } ); - Button btn_path=new Button(context); - btn_path.setText("Find command"); - btn_path.setOnClickListener( - new View.OnClickListener() - { - public void onClick(View p1) - { - String lastPath=SP.getString("lastPath", null); - File get= (lastPath==null) - ?Environment.getExternalStorageDirectory() - :new File(lastPath).getParentFile(); - startActivityForResult( - new Intent() - .setClass(getApplicationContext(), jackpal.androidterm.shortcuts.FSNavigator.class) - .setData(Uri.fromFile(get)) - .putExtra("title", "SELECT SHORTCUT TARGET") - , OP_MAKE_SHORTCUT - ); - } - } - ); + Button btn_path=new Button(context); + btn_path.setText("Find command"); + btn_path.setOnClickListener( + new View.OnClickListener() + { + public void onClick(View p1) + { + String lastPath=SP.getString("lastPath", null); + File get= (lastPath==null) + ?Environment.getExternalStorageDirectory() + :new File(lastPath).getParentFile(); + startActivityForResult( + new Intent() + .setClass(getApplicationContext(), jackpal.androidterm.shortcuts.FSNavigator.class) + .setData(Uri.fromFile(get)) + .putExtra("title", "SELECT SHORTCUT TARGET") + , OP_MAKE_SHORTCUT + ); + } + } + ); lv.addView( layoutTextViewH( "Command window requires full path, no arguments. For other commands use Arguments window (ex: cd /sdcard)." @@ -138,7 +123,9 @@ public void onClick(View p1) { public void onClick(View p1) { +try{ new ColorValue(context, img, iconText); +}catch(Exception e){android.widget.Toast.makeText(context, e.toString(), android.widget.Toast.LENGTH_LONG).show();} } } ); @@ -229,7 +216,6 @@ void buildShortcut( Intent target= new Intent().setClassName(pkg_jackpal, pkg_jackpal+".RemoteInterface"); target.setAction(pkg_jackpal+".RUN_SCRIPT"); target.setDataAndType(uri, "text/plain"); -// target.putExtra(pkg_jackpal+".iInitialCommand", path); target.putExtra(pkg_jackpal+".window_handle", shortcutName); target.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); Intent wrapper= new Intent(); diff --git a/src/jackpal/androidterm/shortcuts/ColorValue.java b/src/jackpal/androidterm/shortcuts/ColorValue.java index 0f18a58d9..d5610cbf1 100755 --- a/src/jackpal/androidterm/shortcuts/ColorValue.java +++ b/src/jackpal/androidterm/shortcuts/ColorValue.java @@ -1,3 +1,4 @@ +//From the desk of Frank P. Westlake; public domain. package jackpal.androidterm.shortcuts; import android.app. AlertDialog; @@ -16,31 +17,21 @@ ////////////////////////////////////////////////////////////////////// public class ColorValue - //implements View.OnKeyListener - //, CompoundButton.OnCheckedChangeListener implements CompoundButton.OnCheckedChangeListener { - Context context; - SeekBar[] sb= {null, null, null, null, null}; - EditText[] value= {null, null, null}; - int[] start= {0, 0, 0}; - int[] current= {0, 0, 0}; - int[] color= {0xFF, 0, 0, 0}; -// int which= 0; - boolean started= false; - AlertDialog alert; - AlertDialog.Builder builder; - TextView lt= null; - boolean barLock= false; - CheckBox[] lk= {null, null, null, null}; - String Title= "MAKE TEXT ICON"; - boolean[] locks= {false, false, false, false}; - final int FP= LinearLayout.LayoutParams.FILL_PARENT; - final int WC= LinearLayout.LayoutParams.WRAP_CONTENT; - final ImageView imgview; - final String result[]; - private String imgtext=""; -// private int build_version= android.os.Build.VERSION.SDK_INT; + private final Context context; + private EditText value; + private final int[] color= {0xFF, 0, 0, 0}; + private boolean started= false; + private AlertDialog.Builder builder; + private boolean barLock= false; + private final String Title= "MAKE TEXT ICON"; + private final boolean[] locks= {false, false, false, false}; + private final int FP= LinearLayout.LayoutParams.FILL_PARENT; + private final int WC= LinearLayout.LayoutParams.WRAP_CONTENT; + private final ImageView imgview; + private final String result[]; + private String imgtext=""; //////////////////////////////////////////////////////////// public ColorValue(Context context, final ImageView imgview, final String result[]) @@ -53,34 +44,34 @@ public ColorValue(Context context, final ImageView imgview, final String result[ } public void colorValue() { -// if(build_version>=14) builder=new AlertDialog.Builder(context, AlertDialog.THEME_DEVICE_DEFAULT_DARK); -// else if(build_version>=11) builder=new AlertDialog.Builder(context, AlertDialog.THEME_TRADITIONAL); -// else builder=new AlertDialog.Builder(context); - final TextView hexWindow[]=new TextView[4]; - builder=new AlertDialog.Builder(context); + final int arraySizes= 4; + builder= new AlertDialog.Builder(context); LinearLayout lv=new LinearLayout(context); lv.setOrientation(LinearLayout.VERTICAL); String lab[]={"α ", "R ", "G ", "B "}; int clr[]={0xFFFFFFFF, 0xFFFF0000, 0xFF00FF00, 0xFF0000FF}; - for(int i=0, n=(Integer)imgview.getTag(); i<4; i++) color[i]=(n>>(24-i*8))&0xFF; - lt=new TextView(context); - lt.setText("LOCK"); - lt.setPadding(lt.getPaddingLeft(), lt.getPaddingTop(), 5, lt.getPaddingBottom()); - lt.setGravity(Gravity.RIGHT); - for(int i=0, n=value.length; i>(24-i*8))&0xFF; + TextView lt=new TextView(context); + lt.setText("LOCK"); + lt.setPadding(lt.getPaddingLeft(), lt.getPaddingTop(), 5, lt.getPaddingBottom()); + lt.setGravity(Gravity.RIGHT); + value=new EditText(context); + value.setText(imgtext); + value.setSingleLine(true); + value.setGravity(Gravity.CENTER); + value.setTextColor((Integer)imgview.getTag()); + value.setBackgroundColor((0xFF<<24)|0x007799); LinearLayout vh=new LinearLayout(context); vh.setOrientation(LinearLayout.HORIZONTAL); vh.setGravity(Gravity.CENTER_HORIZONTAL); - vh.addView(value[0]); - value[0].setHint("Enter icon text"); + vh.addView(value); + value.setHint("Enter icon text"); lv.addView(vh); lv.addView(lt); - for(int i=0, n=sb.length-1; i Date: Sun, 11 May 2014 07:23:46 -0700 Subject: [PATCH 676/847] Shortcut strings exported. --- res/values/strings.xml | 23 ++++++++++++++++++ .../androidterm/shortcuts/AddShortcut.java | 24 +++++++++---------- .../androidterm/shortcuts/ColorValue.java | 8 ++++++- .../androidterm/shortcuts/FSNavigator.java | 14 +++++------ 4 files changed, 49 insertions(+), 20 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 614289418..76541a605 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -165,4 +165,27 @@ Help http://jackpal.github.com/Android-Terminal-Emulator/help/index.html + + + FILE SELECTOR + External storage not available + Or enter path here. + Change theme + + command + --example=\""a\" + Find command + SELECT SHORTCUT TARGET + Command window requires full path, no arguments. For other commands use Arguments window (ex: cd /sdcard). + Arguments\: + Shortcut label\: + Text icon + + Term shortcut + + α + R + G + B + diff --git a/src/jackpal/androidterm/shortcuts/AddShortcut.java b/src/jackpal/androidterm/shortcuts/AddShortcut.java index 602f8044f..d4ca3b004 100755 --- a/src/jackpal/androidterm/shortcuts/AddShortcut.java +++ b/src/jackpal/androidterm/shortcuts/AddShortcut.java @@ -20,6 +20,8 @@ import android.widget. ScrollView; import android.widget. TextView; import android.widget. EditText; +import jackpal.androidterm.R; + import java.io. File; public class AddShortcut @@ -56,9 +58,9 @@ void makeShortcut() lv.setOrientation(LinearLayout.VERTICAL); for(int i=0, n=et.length; i>(24-i*8))&0xFF; TextView lt=new TextView(context); diff --git a/src/jackpal/androidterm/shortcuts/FSNavigator.java b/src/jackpal/androidterm/shortcuts/FSNavigator.java index 0ab385678..1aef5d655 100755 --- a/src/jackpal/androidterm/shortcuts/FSNavigator.java +++ b/src/jackpal/androidterm/shortcuts/FSNavigator.java @@ -26,12 +26,12 @@ public class FSNavigator extends android.app.Activity { - public final int ACTION_THEME_SWAP= 0x00000100; - final int BUTTON_SIZE= 150; + private final int ACTION_THEME_SWAP= 0x00000100; + private final int BUTTON_SIZE= 150; private android.content.Context context= this; private File cd= null; private float textLg= 24; - public SharedPreferences SP= null; + private SharedPreferences SP= null; private int theme= android.R.style.Theme; private File extSdCardFile; private String extSdCard; @@ -40,7 +40,7 @@ public class FSNavigator public void onCreate(android.os.Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setTitle("File Selector"); + setTitle(getString(R.string.fsnavigator_title));//"File Selector"); SP=PreferenceManager.getDefaultSharedPreferences(context); theme=SP.getInt("theme", theme); setTheme(theme); @@ -99,7 +99,7 @@ private String ifAvailable(String goTo) { return(goTo); } - toast("External storage not available", 1); + toast(getString(R.string.fsnavigator_no_external_storage), 1);//"External storage not available", 1); return(extSdCard); } return(goTo); @@ -157,7 +157,7 @@ public View fileEntry(final String entry) if(newFile) { tv=new EditText(context); - tv.setHint("Or enter path here."); + tv.setHint(getString(R.string.fsnavigator_optional_enter_path));//"Or enter path here."); tv.setLayoutParams( new LinearLayout.LayoutParams( LinearLayout.LayoutParams.FILL_PARENT @@ -412,7 +412,7 @@ public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); // super.onPrepareOptionsMenu(menu); menu.clear(); - menu.add(0, ACTION_THEME_SWAP, 0, "Change theme"); + menu.add(0, ACTION_THEME_SWAP, 0, getString(R.string.fsnavigator_change_theme));//"Change theme"); return(true); } ////////////////////////////////////////////////////////////////////// From b5782f60e236322197cff2b33eb352013830816c Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Tue, 6 May 2014 02:31:38 -0700 Subject: [PATCH 677/847] EmulatorView: fix off-by-one error in linkification bounds Links currently don't extend to the last character of the link text (this is easy to check, either with a pixel-accurate pointer device such as a Bluetooth mouse or in the emulator) -- fix this. --- .../src/jackpal/androidterm/emulatorview/EmulatorView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/EmulatorView.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/EmulatorView.java index 167ac49c7..2efddb882 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/EmulatorView.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/EmulatorView.java @@ -315,7 +315,7 @@ private int createLinks(int row) { URLSpan url = urls[urlNum]; int spanStart = textToLinkify.getSpanStart(url); - int spanEnd = textToLinkify.getSpanEnd(url) - 1; + int spanEnd = textToLinkify.getSpanEnd(url); //Build accurate indices for multi-line links int startRow = spanStart / mColumns; From 6b4ed0ddbe550e97664d9c28812b406f9b0bee20 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Tue, 6 May 2014 03:27:20 -0700 Subject: [PATCH 678/847] EmulatorView: fix linkification on non-basic lines Linkification currently assumes that each char in the array representing the line maps one-to-one onto a screen column. However, this is demonstrably false in a Unicode environment, where one screen column can take an arbitrary number of chars (surrogate pairs and/or combining characters) and one char can take two screen columns (East Asian wide characters in the BMP). As a result, links end up misplaced on lines where these more advanced Unicode features are in use. To fix this, we need to properly determine the screen columns the link spans, taking into account the above; unfortunately, there appears to be no way short of iterating over the entire line up to that point to discover this. To lessen the performance hit, we add support to UnicodeTranscript and TranscriptScreen to allow EmulatorView to determine if scanning the entire line is necessary and only scan the line if we must. --- .../emulatorview/EmulatorView.java | 97 ++++++++++++++++--- .../emulatorview/TranscriptScreen.java | 16 ++- .../emulatorview/UnicodeTranscript.java | 8 ++ 3 files changed, 106 insertions(+), 15 deletions(-) diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/EmulatorView.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/EmulatorView.java index 2efddb882..926e87a66 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/EmulatorView.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/EmulatorView.java @@ -266,14 +266,30 @@ private boolean startsWith(CharSequence s, int start, int end, private int createLinks(int row) { TranscriptScreen transcriptScreen = mEmulator.getScreen(); - char [] result = transcriptScreen.getScriptLine(row); + char [] line = transcriptScreen.getScriptLine(row); int lineCount = 1; //Nothing to do if there's no text. - if(result == null) + if(line == null) return lineCount; - SpannableStringBuilder textToLinkify = new SpannableStringBuilder(new String(result)); + /* If this is not a basic line, the array returned from getScriptLine() + * could have arbitrary garbage at the end -- find the point at which + * the line ends and only include that in the text to linkify. + * + * XXX: The fact that the array returned from getScriptLine() on a + * basic line contains no garbage is an implementation detail -- the + * documented behavior explicitly allows garbage at the end! */ + int lineLen; + boolean textIsBasic = transcriptScreen.isBasicLine(row); + if (textIsBasic) { + lineLen = line.length; + } else { + // The end of the valid data is marked by a NUL character + for (lineLen = 0; line[lineLen] != 0; ++lineLen); + } + + SpannableStringBuilder textToLinkify = new SpannableStringBuilder(new String(line, 0, lineLen)); boolean lineWrap = transcriptScreen.getScriptLineWrap(row); @@ -281,16 +297,28 @@ private int createLinks(int row) while (lineWrap) { //Get next line - result = transcriptScreen.getScriptLine(row + lineCount); + int nextRow = row + lineCount; + line = transcriptScreen.getScriptLine(nextRow); //If next line is blank, don't try and append - if(result == null) + if(line == null) break; - textToLinkify.append(new String(result)); + boolean lineIsBasic = transcriptScreen.isBasicLine(nextRow); + if (textIsBasic && !lineIsBasic) { + textIsBasic = lineIsBasic; + } + if (lineIsBasic) { + lineLen = line.length; + } else { + // The end of the valid data is marked by a NUL character + for (lineLen = 0; line[lineLen] != 0; ++lineLen); + } + + textToLinkify.append(new String(line, 0, lineLen)); //Check if line after next is wrapped - lineWrap = transcriptScreen.getScriptLineWrap(row + lineCount); + lineWrap = transcriptScreen.getScriptLineWrap(nextRow); ++lineCount; } @@ -299,6 +327,8 @@ private int createLinks(int row) URLSpan [] urls = textToLinkify.getSpans(0, textToLinkify.length(), URLSpan.class); if(urls.length > 0) { + int columns = mColumns; + //re-index row to 0 if it is negative int screenRow = row - mTopRow; @@ -306,7 +336,7 @@ private int createLinks(int row) URLSpan [][] linkRows = new URLSpan[lineCount][]; for(int i=0; i= columns) { + ++startRow; + startCol %= columns; + } + } + + endRow = startRow; + endCol = startCol; + for (int i = spanStart; i < spanEnd; ++i) { + char c = textToLinkify.charAt(i); + if (Character.isHighSurrogate(c)) { + ++i; + endCol += UnicodeTranscript.charWidth(c, textToLinkify.charAt(i)); + } else { + endCol += UnicodeTranscript.charWidth(c); + } + if (endCol >= columns) { + ++endRow; + endCol %= columns; + } + } + } //Fill linkRows with the URL where appropriate for(int i=startRow; i <= endRow; ++i) diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TranscriptScreen.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TranscriptScreen.java index 8c4430850..4da94c02d 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TranscriptScreen.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TranscriptScreen.java @@ -433,7 +433,7 @@ public void resize(int columns, int rows, int style) { * @param row The row index to be queried * @return The line of text at this row index */ - public char[] getScriptLine(int row) + char[] getScriptLine(int row) { try { @@ -454,8 +454,20 @@ public char[] getScriptLine(int row) * @param row The row to check for line-wrap status * @return The line wrap status of the row provided */ - public boolean getScriptLineWrap(int row) + boolean getScriptLineWrap(int row) { return mData.getLineWrap(row); } + + /** + * Get whether the line at this index is "basic" (contains only BMP + * characters of width 1). + */ + boolean isBasicLine(int row) { + if (mData != null) { + return mData.isBasicLine(row); + } else { + return true; + } + } } diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/UnicodeTranscript.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/UnicodeTranscript.java index 926df38fc..ca9111b64 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/UnicodeTranscript.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/UnicodeTranscript.java @@ -650,6 +650,14 @@ public StyleRow getLineColor(int row) { return getLineColor(row, 0, mColumns); } + boolean isBasicLine(int row) { + if (row < -mActiveTranscriptRows || row > mScreenRows-1) { + throw new IllegalArgumentException(); + } + + return (mLines[externalToInternalRow(row)] instanceof char[]); + } + public boolean getChar(int row, int column) { return getChar(row, column, 0); } From bed0d548cacce51e48447033bce9911d0b06d456 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Sun, 11 May 2014 17:23:03 -0700 Subject: [PATCH 679/847] Remove some cruft from the tree --- pushnote | 8 -------- 1 file changed, 8 deletions(-) delete mode 100755 pushnote diff --git a/pushnote b/pushnote deleted file mode 100755 index 7cb9bc241..000000000 --- a/pushnote +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash -work="/home/frank/a/Android-Terminal-Emulator" -note="/note3/storage/extSdCard/java/a/Android-Terminal-Emulator" - -chown -R frank:programmer "$work" - -mirror "$work/*" "$note" -tonote3 "$note" From 846d77fe7f6d942da2c61927305ccac82dbdeef0 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Sun, 4 May 2014 21:49:10 -0700 Subject: [PATCH 680/847] EmulatorView library: tidy up public API GrowableIntArray and TextStyle are part of the library's internals and not exposed through any of the intended public APIs, so they should be marked package-private. --- .../jackpal/androidterm/emulatorview/GrowableIntArray.java | 6 +++--- .../src/jackpal/androidterm/emulatorview/TextStyle.java | 7 ++++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/GrowableIntArray.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/GrowableIntArray.java index ba8b5596f..9576ae713 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/GrowableIntArray.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/GrowableIntArray.java @@ -1,12 +1,12 @@ package jackpal.androidterm.emulatorview; -public class GrowableIntArray { - public GrowableIntArray(int initalCapacity) { +class GrowableIntArray { + GrowableIntArray(int initalCapacity) { mData = new int[initalCapacity]; mLength = 0; } - public void append(int i) { + void append(int i) { if (mLength + 1 > mData.length) { int newLength = Math.max((mData.length * 3) >> 1, 16); int[] temp = new int[newLength]; diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TextStyle.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TextStyle.java index 1f2022d69..bf16e4356 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TextStyle.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TextStyle.java @@ -1,6 +1,6 @@ package jackpal.androidterm.emulatorview; -public final class TextStyle { +final class TextStyle { // Effect bitmasks: final static int fxNormal = 0; final static int fxBold = 1; // Originally Bright @@ -36,4 +36,9 @@ static int decodeBackColor(int encodedColor) { static int decodeEffect(int encodedColor) { return (encodedColor >> 18) & 0x3f; } + + private TextStyle() { + // Prevent instantiation + throw new UnsupportedOperationException(); + } } From 91688f4272a2c3bdade758e25d88718b2d7ae654 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Sun, 4 May 2014 21:37:01 -0700 Subject: [PATCH 681/847] TermSession: update documentation for notifyTitleChanged() --- .../src/jackpal/androidterm/emulatorview/TermSession.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TermSession.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TermSession.java index b2d586b23..6555859eb 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TermSession.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TermSession.java @@ -412,8 +412,6 @@ public void setTitleChangedListener(UpdateCallback listener) { /** * Notify the UpdateCallback registered for title changes, if any, that the * terminal session's title has changed. - * - * @param title The terminal's new title. */ protected void notifyTitleChanged() { UpdateCallback listener = mTitleChangedListener; From a140a98ff4f62ec4af99f38624764b5f953ee1cc Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Sun, 4 May 2014 23:37:57 -0700 Subject: [PATCH 682/847] ColorScheme: update documentation --- .../androidterm/emulatorview/ColorScheme.java | 26 ++++++------------- 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/ColorScheme.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/ColorScheme.java index e264695a1..d81f1f1c2 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/ColorScheme.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/ColorScheme.java @@ -17,26 +17,16 @@ package jackpal.androidterm.emulatorview; /** - * A class describing a color scheme for a 16-color VT100 terminal. + * A class describing a color scheme for an {@link EmulatorView}. *

- * A 16-color VT100 has two separate color maps, one for foreground colors and - * one for background colors. Each one contains eight colors, which are - * traditionally found in the following order: - *

- * { black, red, green, yellow, blue, magenta, cyan, white } - *

- * In addition, each of the foreground colors has a corresponding "bright" - * version. Traditionally, the "dim" white is actually a light gray, while - * the "bright" black is a dark gray color. - *

- * {@link EmulatorView} supports limited changes to the default color maps - * via the color scheme mechanism. Passing a ColorScheme to + * EmulatorView supports changing its default foreground, + * background, and cursor colors. Passing a ColorScheme to * {@link EmulatorView#setColorScheme setColorScheme} will cause the - * foreground color with map index foreColorIndex to be replaced - * with the provided foreColor, and the background color with map - * index backColorIndex to be replaced with the provided - * backColor. The provided colors will then become the default - * foreground and background colors for the EmulatorView. + * EmulatorView to use the specified colors as its defaults. + *

+ * Cursor colors can be omitted when specifying a color scheme; if no cursor + * colors are specified, ColorScheme will automatically select + * suitable cursor colors for you. * * @see EmulatorView#setColorScheme */ From 189b2e478bf082fd2c25a2fdc43e55237812d00e Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Sun, 4 May 2014 23:41:48 -0700 Subject: [PATCH 683/847] EmulatorView: update documentation for getURLat() --- .../src/jackpal/androidterm/emulatorview/EmulatorView.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/EmulatorView.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/EmulatorView.java index 167ac49c7..d83387588 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/EmulatorView.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/EmulatorView.java @@ -1576,11 +1576,12 @@ public void setMouseTracking(boolean flag) { /** + * Get the URL for the link displayed at the specified screen coordinates. * - * Get the URL at the specified screen coordinates and return its string value * @param x The x coordinate being queried (from 0 to screen width) * @param y The y coordinate being queried (from 0 to screen height) - * @return + * @return The URL for the link at the specified screen coordinates, or + * null if no link exists there. */ public String getURLat(float x, float y) { From 0b1b4bc023f36377b2a9956d5c1ccb5c5e566e5d Mon Sep 17 00:00:00 2001 From: FrankWestlake Date: Mon, 12 May 2014 07:42:48 -0700 Subject: [PATCH 684/847] Some neglected string exports. --- AndroidManifest.xml | 2 +- res/values/strings.xml | 4 ++++ src/jackpal/androidterm/shortcuts/ColorValue.java | 12 ++++++------ 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 5de2800e9..6fad25476 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -85,7 +85,7 @@ android:label="@string/window_list" /> Help http://jackpal.github.com/Android-Terminal-Emulator/help/index.html + + "Term shortcut" FILE SELECTOR External storage not available @@ -187,5 +189,7 @@ R G B + LOCK + Enter icon text diff --git a/src/jackpal/androidterm/shortcuts/ColorValue.java b/src/jackpal/androidterm/shortcuts/ColorValue.java index 76042bd4e..90805bc7c 100755 --- a/src/jackpal/androidterm/shortcuts/ColorValue.java +++ b/src/jackpal/androidterm/shortcuts/ColorValue.java @@ -50,15 +50,15 @@ public void colorValue() LinearLayout lv=new LinearLayout(context); lv.setOrientation(LinearLayout.VERTICAL); String lab[]={ - context.getString(R.string.colorvalue_letter_alpha)+" " //"α " - , context.getString(R.string.colorvalue_letter_red)+" " //"R " - , context.getString(R.string.colorvalue_letter_green)+" " //"G " - , context.getString(R.string.colorvalue_letter_blue)+" " //"B " + context.getString(R.string.colorvalue_letter_alpha) +" " //"α " + , context.getString(R.string.colorvalue_letter_red) +" " //"R " + , context.getString(R.string.colorvalue_letter_green) +" " //"G " + , context.getString(R.string.colorvalue_letter_blue) +" " //"B " }; int clr[]={0xFFFFFFFF, 0xFFFF0000, 0xFF00FF00, 0xFF0000FF}; for(int i=0, n=(Integer)imgview.getTag(); i>(24-i*8))&0xFF; TextView lt=new TextView(context); - lt.setText("LOCK"); + lt.setText(context.getString(R.string.colorvalue_label_lock_button_column));//"LOCK"); lt.setPadding(lt.getPaddingLeft(), lt.getPaddingTop(), 5, lt.getPaddingBottom()); lt.setGravity(Gravity.RIGHT); value=new EditText(context); @@ -71,7 +71,7 @@ public void colorValue() vh.setOrientation(LinearLayout.HORIZONTAL); vh.setGravity(Gravity.CENTER_HORIZONTAL); vh.addView(value); - value.setHint("Enter icon text"); + value.setHint(context.getString(R.string.colorvalue_icon_text_entry_hint));//"Enter icon text"); lv.addView(vh); lv.addView(lt); final SeekBar sb[]= new SeekBar[arraySizes+1]; From e414a91e3c776489638458eaa98138fd6fc38c5b Mon Sep 17 00:00:00 2001 From: FrankWestlake Date: Mon, 12 May 2014 08:57:44 -0700 Subject: [PATCH 685/847] String export. --- AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 6fad25476..fdae8f905 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -61,7 +61,7 @@ From 9147608d7071ff928f1d0bfbcfbedb21dc4f451c Mon Sep 17 00:00:00 2001 From: FrankWestlake Date: Thu, 15 May 2014 09:03:10 -0700 Subject: [PATCH 686/847] Shortcuts: recyclable views. --- .gitignore | 2 + lint.xml | 1 + res/values/id.xml | 5 +- res/values/strings.xml | 1 + src/jackpal/androidterm/Term.java | 0 .../androidterm/shortcuts/FSNavigator.java | 382 +++++++++++------- 6 files changed, 243 insertions(+), 148 deletions(-) mode change 100644 => 100755 src/jackpal/androidterm/Term.java diff --git a/.gitignore b/.gitignore index 8953da8bb..8ca6dd02a 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,8 @@ obj/ .project .settings/ examples/widget/assets/ +pushnote +pushate # Generated by tools/update.sh build.xml diff --git a/lint.xml b/lint.xml index ee0eead5b..48d75f955 100644 --- a/lint.xml +++ b/lint.xml @@ -1,3 +1,4 @@ + \ No newline at end of file diff --git a/res/values/id.xml b/res/values/id.xml index b07728184..342991ebf 100644 --- a/res/values/id.xml +++ b/res/values/id.xml @@ -1,4 +1,7 @@ - + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index 4883f371c..aa99c2bc5 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -165,6 +165,7 @@ Help http://jackpal.github.com/Android-Terminal-Emulator/help/index.html + Term here "Term shortcut" diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java old mode 100644 new mode 100755 diff --git a/src/jackpal/androidterm/shortcuts/FSNavigator.java b/src/jackpal/androidterm/shortcuts/FSNavigator.java index 1aef5d655..e404c5063 100755 --- a/src/jackpal/androidterm/shortcuts/FSNavigator.java +++ b/src/jackpal/androidterm/shortcuts/FSNavigator.java @@ -21,20 +21,30 @@ import android.widget. Toast; import java.io. File; import java.io. IOException; +import java.util. HashMap; import jackpal.androidterm. R; public class FSNavigator extends android.app.Activity { - private final int ACTION_THEME_SWAP= 0x00000100; - private final int BUTTON_SIZE= 150; - private android.content.Context context= this; - private File cd= null; - private float textLg= 24; - private SharedPreferences SP= null; - private int theme= android.R.style.Theme; - private File extSdCardFile; - private String extSdCard; + private final int ACTION_THEME_SWAP= 0x00000100; + private final int BUTTON_SIZE= 150; + private android.content.Context context= this; + private float textLg= 24; + private int theme= android.R.style.Theme; + private SharedPreferences SP; + private File cd; + private File extSdCardFile; + private String extSdCard; + private HashMap cachedFileView; + private HashMap cachedDirectoryView; + private HashMap cachedDividerView; + private int countFileView; + private int countDirectoryView; + private int countDividerView; + private LinearLayout contentView; + private LinearLayout titleView; + private LinearLayout pathEntryView; //////////////////////////////////////////////////////////// public void onCreate(android.os.Bundle savedInstanceState) @@ -51,6 +61,13 @@ public void onCreate(android.os.Bundle savedInstanceState) extSdCard=getCanonicalPath(extSdCardFile); if(null==(chdir(intent.getData().getPath()))) chdir(extSdCard); if(intent.hasExtra("title")) setTitle(intent.getStringExtra("title")); + + titleView= directoryEntry(".."); + pathEntryView= fileEntry(null); + contentView= makeContentView(); + cachedDirectoryView= new HashMap(); + cachedFileView= new HashMap(); + cachedDividerView= new HashMap(); } //////////////////////////////////////////////////////////// public void onPause() @@ -59,7 +76,7 @@ public void onPause() doPause(); } //////////////////////////////////////////////////////////// - public void doPause() + private void doPause() { SP.edit().putString("lastDirectory", getCanonicalPath(cd)).commit(); } @@ -118,46 +135,57 @@ private File chdir(String path) //////////////////////////////////////////////////////////// private TextView entryDividerH() { - LinearLayout.LayoutParams btn_params=new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.FILL_PARENT - , 1 - , 1 - ); - TextView b1=new TextView(context); - b1.setLayoutParams(btn_params); - return(b1); + TextView tv; + if(countDividerView stringSortComparator=new java.util.Comparator() @@ -422,7 +510,7 @@ public boolean onOptionsItemSelected(MenuItem item) return(doOptionsItem(item.getItemId())); } ////////////////////////////////////////////////////////////////////// - public boolean doOptionsItem(int itemId) + private boolean doOptionsItem(int itemId) { switch(itemId) { @@ -431,8 +519,8 @@ public boolean doOptionsItem(int itemId) return(false); } ////////////////////////////////////////////////////////////////////// - public void toast(final String message){toast(message, 0);} - public void toast(final String message, final int duration) +// private void toast(final String message){toast(message, 0);} + private void toast(final String message, final int duration) { runOnUiThread( new Runnable() From fa9cd04b7f51be4b5156bf5754ea9bd003816a02 Mon Sep 17 00:00:00 2001 From: FrankWestlake Date: Fri, 16 May 2014 07:53:37 -0700 Subject: [PATCH 687/847] Shortcuts: Recyclable view -- fixed titleView.. --- src/jackpal/androidterm/shortcuts/FSNavigator.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/jackpal/androidterm/shortcuts/FSNavigator.java b/src/jackpal/androidterm/shortcuts/FSNavigator.java index e404c5063..364acf238 100755 --- a/src/jackpal/androidterm/shortcuts/FSNavigator.java +++ b/src/jackpal/androidterm/shortcuts/FSNavigator.java @@ -375,8 +375,8 @@ private LinearLayout directoryEntry(final String name) TextView tv=((TextView)ll.findViewById(R.id.textview)); tv.setTag(name); - tv.setText(up? "["+cd.getPath().trim()+"]" - : name + tv.setText(up ? "["+cd.getPath()+"]" + : name ); ((ImageView)ll.findViewById(R.id.imageview)).setTag(name); return(ll); @@ -451,6 +451,7 @@ private void makeView() else { titleView.setVisibility(View.VISIBLE); + titleView.requestLayout(); ((TextView)titleView.findViewById(R.id.textview)).setText("["+cd.getPath()+"]"); } @@ -477,7 +478,7 @@ private void makeView() } ((TextView)pathEntryView.findViewById(R.id.textview)).setText(""); sv.scrollTo(0, 0); - titleView.setSelected(true); +// titleView.setSelected(true); setContentView(contentView); } ////////////////////////////////////////////////////////////////////// From 20e0bb615d4d226a9208b5028d693c7d511e4125 Mon Sep 17 00:00:00 2001 From: Marc Bassil Date: Sun, 18 May 2014 10:19:11 -0400 Subject: [PATCH 688/847] Added calculation for visible rows/columns, this was useful for me to set the correct PTYsize. Added function to get these values. --- .../emulatorview/EmulatorView.java | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/EmulatorView.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/EmulatorView.java index e1621649f..2a4843edd 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/EmulatorView.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/EmulatorView.java @@ -141,6 +141,11 @@ public class EmulatorView extends View implements GestureDetector.OnGestureListe private int mVisibleColumns; + /* + * The number of rows that are visible on the view + */ + private int mVisibleRows; + /** * The top row of text to display. Ranges from -activeTranscriptRows to 0 */ @@ -1007,6 +1012,26 @@ public int getVisibleHeight() { return mVisibleHeight; } + /** + * Gets the visible number of rows for the view, useful when updating Ptysize with the correct number of rows/columns + * @return The rows for the visible number of rows, this is calculate in updateSize(int w, int h), please call + * updateSize(true) if the view changed, to get the correct calculation before calling this. + */ + public int getVisibleRows() + { + return mVisibleRows; + } + + /** + * Gets the visible number of columns for the view, again useful to get when updating PTYsize + * @return the columns for the visisble view, please call updateSize(true) to re-calculate this if the view has changed + */ + public int getVisibleColumns() + { + return mVisibleColumns; + } + + /** * Page the terminal view (scroll it up or down by delta * screenfuls). @@ -1441,10 +1466,11 @@ protected void onSizeChanged(int w, int h, int oldw, int oldh) { private void updateSize(int w, int h) { mColumns = Math.max(1, (int) (((float) w) / mCharacterWidth)); - mVisibleColumns = (int) (((float) mVisibleWidth) / mCharacterWidth); + mVisibleColumns = Math.max(1, (int) (((float) mVisibleWidth) / mCharacterWidth)); mTopOfScreenMargin = mTextRenderer.getTopMargin(); mRows = Math.max(1, (h - mTopOfScreenMargin) / mCharacterHeight); + mVisibleRows = Math.max(1, (mVisibleHeight - mTopOfScreenMargin) / mCharacterHeight); mTermSession.updateSize(mColumns, mRows); // Reset our paging: From 7970a61dbeec4182a5f215d5d5ad380acc5d73af Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sun, 18 May 2014 10:15:12 -0700 Subject: [PATCH 689/847] Remove out-of-date text version of readme. --- README.txt | 35 ----------------------------------- 1 file changed, 35 deletions(-) delete mode 100644 README.txt diff --git a/README.txt b/README.txt deleted file mode 100644 index dd6403c39..000000000 --- a/README.txt +++ /dev/null @@ -1,35 +0,0 @@ -This is an Android Terminal emulator. It enables you to access your Android -device's built-in command-line shell. - -This terminal emulator emulates Digital Equipment Corporation VT-100 terminal -escape codes. It is designed to be used with command-line programs and -curses-based applications like vi, emacs, nethack, and dropbear ssh. - -Features: - -+ Connects to your Android device's built-in command-line shell. -+ Comprehensive VT100 terminal emulation. -+ Supports a range of font sizes -+ Supports several choices of text and background color. -+ 1000-line transcript buffer. -+ Can send transcript as an email message. - -FAQ: - -+ What can I do with this? - -A you can run and control command-line applications on your Android device. - -+ Why would I want to do that? - -For fun! There are a wealth of Linux utility programs that can be made to work -on Android, and this terminal emulator provides a good way of interacting with -them. - -+ Does this application give me "root" access? - -No, it doesn't. It runs as an ordinary application, with no special access -rights. - -+ How can I build this? -Build instructions can be found in the docs directory. From 0b1304be861240c76ec24d6123c6db194c4fdc94 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sun, 18 May 2014 10:18:16 -0700 Subject: [PATCH 690/847] Minor tweaks --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8ccce5a05..af89bb70d 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ #Android Terminal Emulator Android Terminal Emulator is a terminal emulator for communicating with the built-in Android shell. -Emulates a reasonably large subset of Digital Equipment Corporation VT-100 terminal codes, so +It emulates a reasonably large subset of Digital Equipment Corporation VT-100 terminal codes, so that programs like "vi", "Emacs" and "NetHack" will display properly. -This code is based on the "Term" application which is included in the Android source code release. +This code is based on the "Term" application which is included in the Android Open Source Project. (Which I also wrote. :-) ) [Download the Android Terminal Emulator from Google Play](https://play.google.com/store/apps/details?id=jackpal.androidterm) From 3c80cebc183fca8ee0ea151bd91c4516bf18c250 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sun, 18 May 2014 12:18:01 -0700 Subject: [PATCH 691/847] Remove developer-specific entries from .gitignore --- .gitignore | 2 -- 1 file changed, 2 deletions(-) diff --git a/.gitignore b/.gitignore index 8ca6dd02a..8953da8bb 100644 --- a/.gitignore +++ b/.gitignore @@ -6,8 +6,6 @@ obj/ .project .settings/ examples/widget/assets/ -pushnote -pushate # Generated by tools/update.sh build.xml From 38546fb9b8a080472c8218d833ee1caebc8b5833 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sun, 18 May 2014 12:19:09 -0700 Subject: [PATCH 692/847] Use our own stripped-down version of Patterns.java Because Patterns.java is an API level 8 API. --- .../emulatorview/EmulatorView.java | 2 +- .../emulatorview/compat/Patterns.java | 93 +++++++++++++++++++ 2 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 libraries/emulatorview/src/jackpal/androidterm/emulatorview/compat/Patterns.java diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/EmulatorView.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/EmulatorView.java index 2a4843edd..2c389b0ae 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/EmulatorView.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/EmulatorView.java @@ -19,6 +19,7 @@ import jackpal.androidterm.emulatorview.compat.ClipboardManagerCompat; import jackpal.androidterm.emulatorview.compat.ClipboardManagerCompatFactory; import jackpal.androidterm.emulatorview.compat.KeycodeConstants; +import jackpal.androidterm.emulatorview.compat.Patterns; import java.io.IOException; import java.util.Arrays; @@ -38,7 +39,6 @@ import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; -import android.util.Patterns; import android.view.GestureDetector; import android.view.KeyEvent; import android.view.MotionEvent; diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/compat/Patterns.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/compat/Patterns.java new file mode 100644 index 000000000..f48583967 --- /dev/null +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/compat/Patterns.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2014 Jack Palevich + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jackpal.androidterm.emulatorview.compat; + +/** + * Based upon + * + * https://android.googlesource.com/platform/frameworks/base/+/android-4.4.2_r2/core/java/android/util/Patterns.java + * + */ + +import java.util.regex.Pattern; + +public class Patterns { + /** + * Regular expression to match all IANA top-level domains for WEB_URL. + * List accurate as of 2011/07/18. List taken from: + * http://data.iana.org/TLD/tlds-alpha-by-domain.txt + * This pattern is auto-generated by frameworks/ex/common/tools/make-iana-tld-pattern.py + */ + public static final String TOP_LEVEL_DOMAIN_STR_FOR_WEB_URL = + "(?:" + + "(?:aero|arpa|asia|a[cdefgilmnoqrstuwxz])" + + "|(?:biz|b[abdefghijmnorstvwyz])" + + "|(?:cat|com|coop|c[acdfghiklmnoruvxyz])" + + "|d[ejkmoz]" + + "|(?:edu|e[cegrstu])" + + "|f[ijkmor]" + + "|(?:gov|g[abdefghilmnpqrstuwy])" + + "|h[kmnrtu]" + + "|(?:info|int|i[delmnoqrst])" + + "|(?:jobs|j[emop])" + + "|k[eghimnprwyz]" + + "|l[abcikrstuvy]" + + "|(?:mil|mobi|museum|m[acdeghklmnopqrstuvwxyz])" + + "|(?:name|net|n[acefgilopruz])" + + "|(?:org|om)" + + "|(?:pro|p[aefghklmnrstwy])" + + "|qa" + + "|r[eosuw]" + + "|s[abcdeghijklmnortuvyz]" + + "|(?:tel|travel|t[cdfghjklmnoprtvwz])" + + "|u[agksyz]" + + "|v[aceginu]" + + "|w[fs]" + + "|(?:\u03b4\u03bf\u03ba\u03b9\u03bc\u03ae|\u0438\u0441\u043f\u044b\u0442\u0430\u043d\u0438\u0435|\u0440\u0444|\u0441\u0440\u0431|\u05d8\u05e2\u05e1\u05d8|\u0622\u0632\u0645\u0627\u06cc\u0634\u06cc|\u0625\u062e\u062a\u0628\u0627\u0631|\u0627\u0644\u0627\u0631\u062f\u0646|\u0627\u0644\u062c\u0632\u0627\u0626\u0631|\u0627\u0644\u0633\u0639\u0648\u062f\u064a\u0629|\u0627\u0644\u0645\u063a\u0631\u0628|\u0627\u0645\u0627\u0631\u0627\u062a|\u0628\u06be\u0627\u0631\u062a|\u062a\u0648\u0646\u0633|\u0633\u0648\u0631\u064a\u0629|\u0641\u0644\u0633\u0637\u064a\u0646|\u0642\u0637\u0631|\u0645\u0635\u0631|\u092a\u0930\u0940\u0915\u094d\u0937\u093e|\u092d\u093e\u0930\u0924|\u09ad\u09be\u09b0\u09a4|\u0a2d\u0a3e\u0a30\u0a24|\u0aad\u0abe\u0ab0\u0aa4|\u0b87\u0ba8\u0bcd\u0ba4\u0bbf\u0baf\u0bbe|\u0b87\u0bb2\u0b99\u0bcd\u0b95\u0bc8|\u0b9a\u0bbf\u0b99\u0bcd\u0b95\u0baa\u0bcd\u0baa\u0bc2\u0bb0\u0bcd|\u0baa\u0bb0\u0bbf\u0b9f\u0bcd\u0b9a\u0bc8|\u0c2d\u0c3e\u0c30\u0c24\u0c4d|\u0dbd\u0d82\u0d9a\u0dcf|\u0e44\u0e17\u0e22|\u30c6\u30b9\u30c8|\u4e2d\u56fd|\u4e2d\u570b|\u53f0\u6e7e|\u53f0\u7063|\u65b0\u52a0\u5761|\u6d4b\u8bd5|\u6e2c\u8a66|\u9999\u6e2f|\ud14c\uc2a4\ud2b8|\ud55c\uad6d|xn\\-\\-0zwm56d|xn\\-\\-11b5bs3a9aj6g|xn\\-\\-3e0b707e|xn\\-\\-45brj9c|xn\\-\\-80akhbyknj4f|xn\\-\\-90a3ac|xn\\-\\-9t4b11yi5a|xn\\-\\-clchc0ea0b2g2a9gcd|xn\\-\\-deba0ad|xn\\-\\-fiqs8s|xn\\-\\-fiqz9s|xn\\-\\-fpcrj9c3d|xn\\-\\-fzc2c9e2c|xn\\-\\-g6w251d|xn\\-\\-gecrj9c|xn\\-\\-h2brj9c|xn\\-\\-hgbk6aj7f53bba|xn\\-\\-hlcj6aya9esc7a|xn\\-\\-j6w193g|xn\\-\\-jxalpdlp|xn\\-\\-kgbechtv|xn\\-\\-kprw13d|xn\\-\\-kpry57d|xn\\-\\-lgbbat1ad8j|xn\\-\\-mgbaam7a8h|xn\\-\\-mgbayh7gpa|xn\\-\\-mgbbh1a71e|xn\\-\\-mgbc0a9azcg|xn\\-\\-mgberp4a5d4ar|xn\\-\\-o3cw4h|xn\\-\\-ogbpf8fl|xn\\-\\-p1ai|xn\\-\\-pgbs0dh|xn\\-\\-s9brj9c|xn\\-\\-wgbh1c|xn\\-\\-wgbl6a|xn\\-\\-xkc2al3hye2a|xn\\-\\-xkc2dl3a5ee0h|xn\\-\\-yfro4i67o|xn\\-\\-ygbi2ammx|xn\\-\\-zckzah|xxx)" + + "|y[et]" + + "|z[amw]))"; + /** + * Good characters for Internationalized Resource Identifiers (IRI). + * This comprises most common used Unicode characters allowed in IRI + * as detailed in RFC 3987. + * Specifically, those two byte Unicode characters are not included. + */ + public static final String GOOD_IRI_CHAR = + "a-zA-Z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF"; + /** + * Regular expression pattern to match most part of RFC 3987 + * Internationalized URLs, aka IRIs. Commonly used Unicode characters are + * added. + */ + public static final Pattern WEB_URL = Pattern.compile( + "((?:(http|https|Http|Https|rtsp|Rtsp):\\/\\/(?:(?:[a-zA-Z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)" + + "\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,64}(?:\\:(?:[a-zA-Z0-9\\$\\-\\_" + + "\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,25})?\\@)?)?" + + "((?:(?:[" + GOOD_IRI_CHAR + "][" + GOOD_IRI_CHAR + "\\-]{0,64}\\.)+" // named host + + TOP_LEVEL_DOMAIN_STR_FOR_WEB_URL + + "|(?:(?:25[0-5]|2[0-4]" // or ip address + + "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(?:25[0-5]|2[0-4][0-9]" + + "|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(?:25[0-5]|2[0-4][0-9]|[0-1]" + + "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}" + + "|[1-9][0-9]|[0-9])))" + + "(?:\\:\\d{1,5})?)" // plus option port number + + "(\\/(?:(?:[" + GOOD_IRI_CHAR + "\\;\\/\\?\\:\\@\\&\\=\\#\\~" // plus option query params + + "\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_])|(?:\\%[a-fA-F0-9]{2}))*)?" + + "(?:\\b|$)"); // and finally, a word boundary or end of + // input. This is to stop foo.sure from + // matching as foo.su +} \ No newline at end of file From 685e9fcac6cd4eff24c7f1bef4fe406b937dddd9 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Fri, 23 May 2014 09:21:21 -0700 Subject: [PATCH 693/847] Update to v 1.0.60 --- AndroidManifest.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index fdae8f905..337a5266a 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,8 +1,8 @@ From bb6beb2d70216470455fa46e0e0159fd1a9a5632 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1szl=C3=B3=20G=C3=A1rdonyi?= Date: Sat, 24 May 2014 00:59:19 +0200 Subject: [PATCH 694/847] Updated Hungarian translation --- res/values-hu/strings.xml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml index 8a9650134..35ced4b22 100644 --- a/res/values-hu/strings.xml +++ b/res/values-hu/strings.xml @@ -162,4 +162,35 @@ Billentyű-parancsok használata Ctrl-Tab: váltás az ablakok között, Ctrl-Shift-N: új ablak, Ctrl-Shift-V: beillesztés. Billentyű-parancsok letiltva. + + Súgó + http://jackpal.github.com/Android-Terminal-Emulator/help/index.html + + + + "Terminál parancsikon" + + FÁJL MEGNYITÁSA + Külső tároló nem elérhető + vagy adja meg az útvonalat + Téma váltása + + parancs + --példa=\""a\" + Parancs keresése + PARANCS KERESÉSE + A parancs mezőbe teljes útvonalat kell megadni, paraméterek nélkül. A parancs paramétereit a Paraméterek mezőben lehet megadni (pl.: cd /sdcard). + Paraméterek\: + Ikon címkéje\: + Szöveges ikon + + Terminál parancsikon + + α + R + G + B + RÖGZÍT + Adjon meg szöveget az ikonhoz + \ No newline at end of file From 76ede36ee2327da7653525e8113b7a596ce9860e Mon Sep 17 00:00:00 2001 From: pyler Date: Sun, 25 May 2014 10:03:44 +0200 Subject: [PATCH 695/847] Export string to values 1/2 --- src/jackpal/androidterm/shortcuts/ColorValue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jackpal/androidterm/shortcuts/ColorValue.java b/src/jackpal/androidterm/shortcuts/ColorValue.java index 90805bc7c..0ac95f2f4 100755 --- a/src/jackpal/androidterm/shortcuts/ColorValue.java +++ b/src/jackpal/androidterm/shortcuts/ColorValue.java @@ -26,7 +26,6 @@ public class ColorValue private boolean started= false; private AlertDialog.Builder builder; private boolean barLock= false; - private final String Title= "MAKE TEXT ICON"; private final boolean[] locks= {false, false, false, false}; private final int FP= LinearLayout.LayoutParams.FILL_PARENT; private final int WC= LinearLayout.LayoutParams.WRAP_CONTENT; @@ -161,6 +160,7 @@ public void onClick(DialogInterface dialog, int which) buttonHit(which, (color[0]<<24)|(color[1]<<16)|(color[2]<<8)|color[3]); } }; + String Title = context.getString(R.string.addshortcut_make_text_ikon); builder.setTitle(Title); builder.setPositiveButton(android.R.string.yes, ocl); builder.setNegativeButton(android.R.string.cancel, ocl); From b9c1590bd78aba6da02ddc5f1bb623295a918879 Mon Sep 17 00:00:00 2001 From: pyler Date: Sun, 25 May 2014 10:06:06 +0200 Subject: [PATCH 696/847] Export string to values 2/2 --- res/values/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/res/values/strings.xml b/res/values/strings.xml index aa99c2bc5..3b4db5cc5 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -183,6 +183,7 @@ Arguments\: Shortcut label\: Text icon + MAKE TEXT ICON Term shortcut From 77a689aa9901433ba6103d49d56857dcc1e6d6fc Mon Sep 17 00:00:00 2001 From: pyler Date: Sun, 25 May 2014 10:06:27 +0200 Subject: [PATCH 697/847] ehm.. ikon > icon --- src/jackpal/androidterm/shortcuts/ColorValue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jackpal/androidterm/shortcuts/ColorValue.java b/src/jackpal/androidterm/shortcuts/ColorValue.java index 0ac95f2f4..cbeb520cb 100755 --- a/src/jackpal/androidterm/shortcuts/ColorValue.java +++ b/src/jackpal/androidterm/shortcuts/ColorValue.java @@ -160,7 +160,7 @@ public void onClick(DialogInterface dialog, int which) buttonHit(which, (color[0]<<24)|(color[1]<<16)|(color[2]<<8)|color[3]); } }; - String Title = context.getString(R.string.addshortcut_make_text_ikon); + String Title = context.getString(R.string.addshortcut_make_text_icon); builder.setTitle(Title); builder.setPositiveButton(android.R.string.yes, ocl); builder.setNegativeButton(android.R.string.cancel, ocl); From 3f61aa4baa0cea26edb8a98c3fcd549a1686503b Mon Sep 17 00:00:00 2001 From: pyler Date: Sun, 25 May 2014 10:13:19 +0200 Subject: [PATCH 698/847] Updated Slovak translation --- res/values-sk/strings.xml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml index 7dbce490c..3f33c9b49 100644 --- a/res/values-sk/strings.xml +++ b/res/values-sk/strings.xml @@ -161,4 +161,30 @@ Pomocník http://jackpal.github.com/Android-Terminal-Emulator/help/index.html + Otvoriť terminál + + Skratka terminálu + VYBERTE SÚBOR + Nedostupný externý ukladací priestor + Alebo zadajte umiestnenie sem. + Zmeniť tému + + príkaz + --príklad=\""a\" + Nájsť príkaz + VYBERTE CIEĽ SKRATKY + Príkazové okno vyžaduje celé umiestnenie, žiadne argumenty. Pre ostatné príkazy použite okno pre argumenty (napríklad: cd /sdcard). + Argumenty\: + Názov skratky\: + Text ikony + VYTVORIŤ TEXT IKONY + + Skratka terminálu + + α + Č + Z + M + ZAMKNÚŤ + Zadajte text ikony From cf96d36433d4f7178fbc2d2dab80ee9611be1742 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sun, 25 May 2014 10:31:35 -0700 Subject: [PATCH 699/847] Fix error messages. --- tools/build-debug | 4 ++-- tools/build-release | 10 ++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/tools/build-debug b/tools/build-debug index 1f15cd200..78d77fd2f 100755 --- a/tools/build-debug +++ b/tools/build-debug @@ -8,7 +8,7 @@ if [ -z "${ANDROID_NDK_ROOT+xxx}" ]; then fi if [ ! -d "$ANDROID_NDK_ROOT" ]; then - echo "The directory $ANDROID_NDK_ROOT = ${ANDROID_NDK_ROOT} does not exist." + echo "The directory ANDROID_NDK_ROOT = ${ANDROID_NDK_ROOT} does not exist." exit 1 fi @@ -18,7 +18,7 @@ if [ -z "${ANDROID_SDK_ROOT+xxx}" ]; then fi if [ ! -d "$ANDROID_SDK_ROOT" ]; then - echo "The directory $ANDROID_SDK_ROOT = ${ANDROID_NDK_ROOT} does not exist." + echo "The directory ANDROID_SDK_ROOT = ${ANDROID_SDK_ROOT} does not exist." exit 1 fi diff --git a/tools/build-release b/tools/build-release index a0c15e57b..7a989aa44 100755 --- a/tools/build-release +++ b/tools/build-release @@ -5,8 +5,10 @@ # of Android Terminal Emulator. set -e -if [ ! -f "$HOME/Documents/workspace/keystore/jackpal.keystore" ]; then - echo "Release keystore file is missing. Cannot build release build." +export ANDROID_TERMINAL_EMULATOR_KEYSTORE="$HOME/Documents/workspace/keystore/jackpal.keystore" + +if [ ! -f "$ANDROID_TERMINAL_EMULATOR_KEYSTORE" ]; then + echo "Release keystore file is missing at ${ANDROID_TERMINAL_EMULATOR_KEYSTORE}. Cannot build release build." exit 1 fi @@ -16,7 +18,7 @@ if [ -z "${ANDROID_NDK_ROOT+xxx}" ]; then fi if [ ! -d "$ANDROID_NDK_ROOT" ]; then - echo "The directory $ANDROID_NDK_ROOT = ${ANDROID_NDK_ROOT} does not exist." + echo "The directory ANDROID_NDK_ROOT = ${ANDROID_NDK_ROOT} does not exist." exit 1 fi @@ -26,7 +28,7 @@ if [ -z "${ANDROID_SDK_ROOT+xxx}" ]; then fi if [ ! -d "$ANDROID_SDK_ROOT" ]; then - echo "The directory $ANDROID_SDK_ROOT = ${ANDROID_NDK_ROOT} does not exist." + echo "The directory ANDROID_SDK_ROOT = ${ANDROID_SDK_ROOT} does not exist." exit 1 fi From 51c4fe70fb05d87408ee729a7b8763b170d8f865 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sun, 25 May 2014 12:39:25 -0700 Subject: [PATCH 700/847] Fix bad closing tag. --- res/values-sk/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml index 3f33c9b49..ee813c4aa 100644 --- a/res/values-sk/strings.xml +++ b/res/values-sk/strings.xml @@ -166,7 +166,7 @@ Skratka terminálu VYBERTE SÚBOR Nedostupný externý ukladací priestor - Alebo zadajte umiestnenie sem. + Alebo zadajte umiestnenie sem. Zmeniť tému príkaz From b21c1d8e28658b6f29d5933fe18aff88abc420d5 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sun, 25 May 2014 12:48:52 -0700 Subject: [PATCH 701/847] Use default theme for dialog box. --- AndroidManifest.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 337a5266a..4df478f89 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -88,7 +88,6 @@ android:label="@string/activity_shortcut_create" android:name="jackpal.androidterm.shortcuts.AddShortcut" android:launchMode="singleTop" - android:theme="@android:style/Theme.Translucent.NoTitleBar" android:configChanges="orientation|keyboard|keyboardHidden" > From 241f03de58ef00a992fd569af1290b4ec8f46844 Mon Sep 17 00:00:00 2001 From: McGiverGim Date: Mon, 26 May 2014 08:13:27 +0200 Subject: [PATCH 702/847] Update strings --- res/values-es/strings.xml | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml index 58b3a4865..b1d954585 100644 --- a/res/values-es/strings.xml +++ b/res/values-es/strings.xml @@ -163,4 +163,37 @@ Ctrl-Tab: cambiar ventana, Ctrl-Shift-N: nueva ventana, Ctrl-Shift-V: pegar. Atajos de teclado desactivados. + Ayuda + http://jackpal.github.com/Android-Terminal-Emulator/help/index.html + Terminal aquí + + + + "Terminal (acceso directo)" + + EXPLORADOR DE ARCHIVOS + Almacenamiento externo no disponible + O intruduce ruta aquí. + Cambiar tema + + comando + --ejemplo=\""a\" + Encontrar comando + ELIJE DESTINO DEL ACCESO DIRECTO + La ventana de comandos necesita la ruta completa, sin argumentos. Para otros comandos usa la ventana de Argumentos (ej: cd /sdcard). + Argumentos\: + Etiqueta acceso directo\: + Texto del icono + HACER ICONO DE TEXTO + + Acceso directo a terminal + + α + R + G + B + BLOQUEAR + Introduce texto del icono + + From 3fb8de9d6bfa2efbee701106adc6dc9d548386ab Mon Sep 17 00:00:00 2001 From: FrankWestlake Date: Thu, 29 May 2014 08:19:55 -0700 Subject: [PATCH 703/847] Translucent back and Halo dialogs. --- .gitignore | 1 + AndroidManifest.xml | 1 + .../androidterm/compat/AlertDialogCompat.java | 77 +++++++++++++++++++ .../androidterm/shortcuts/AddShortcut.java | 4 +- .../androidterm/shortcuts/ColorValue.java | 5 +- 5 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 src/jackpal/androidterm/compat/AlertDialogCompat.java diff --git a/.gitignore b/.gitignore index 8953da8bb..e7cebe59b 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ obj/ .project .settings/ examples/widget/assets/ +pushnote # Generated by tools/update.sh build.xml diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 4df478f89..337a5266a 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -88,6 +88,7 @@ android:label="@string/activity_shortcut_create" android:name="jackpal.androidterm.shortcuts.AddShortcut" android:launchMode="singleTop" + android:theme="@android:style/Theme.Translucent.NoTitleBar" android:configChanges="orientation|keyboard|keyboardHidden" > diff --git a/src/jackpal/androidterm/compat/AlertDialogCompat.java b/src/jackpal/androidterm/compat/AlertDialogCompat.java new file mode 100644 index 000000000..2539e1d44 --- /dev/null +++ b/src/jackpal/androidterm/compat/AlertDialogCompat.java @@ -0,0 +1,77 @@ +package jackpal.androidterm.compat; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; + +public class AlertDialogCompat extends AlertDialog +{ + // API 11 + public static int THEME_HOLO_TRADITIONAL= 1; + public static int THEME_HOLO_DARK= 2; + public static int THEME_HOLO_LIGHT= 3; + // API 14 + public static int THEME_DEVICE_DEFAULT_DARK= 4; + public static int THEME_DEVICE_DEFAULT_LIGHT= 5; + //////////////////////////////////////////////////////////// + private AlertDialogCompat(Context context) + { + super(context); + } + private AlertDialogCompat(Context context, int theme) + { + super(context, theme); + } + private AlertDialogCompat(Context context, boolean cancelable, DialogInterface.OnCancelListener cancelListener) + { + super(context, cancelable, cancelListener); + } + //////////////////////////////////////////////////////////// + private static class Api11OrLater extends AlertDialog + { + public Api11OrLater(Context context, int theme) + { + super(context, theme); + } + public Api11OrLater(Context context, boolean cancelable, DialogInterface.OnCancelListener cancelListener) + { + super(context, cancelable, cancelListener); + } + } + //////////////////////////////////////////////////////////// + private static class Api14OrLater extends AlertDialog + { + public Api14OrLater(Context context, int theme) + { + super(context, theme); + } + public Api14OrLater(Context context, boolean cancelable, DialogInterface.OnCancelListener cancelListener) + { + super(context, cancelable, cancelListener); + } + } + //////////////////////////////////////////////////////////// + public static AlertDialog newInstance(Context context) + { + return(new AlertDialogCompat(context)); + } + //////////////////////////////////////////////////////////// + public static AlertDialog newInstance(Context context, int theme) + { + if(AndroidCompat.SDK >= 14) + { + return(new Api14OrLater(context, theme)); + } + if(AndroidCompat.SDK >= 11) + { + return(new Api11OrLater(context, theme)); + } + return(new AlertDialogCompat(context, theme)); + } + //////////////////////////////////////////////////////////// + public static AlertDialog newInstance(Context context, boolean cancelable, DialogInterface.OnCancelListener cancelListener) + { + return(new AlertDialogCompat(context, cancelable, cancelListener)); + } + //////////////////////////////////////////////////////////// +} diff --git a/src/jackpal/androidterm/shortcuts/AddShortcut.java b/src/jackpal/androidterm/shortcuts/AddShortcut.java index d4ca3b004..f2707c7c0 100755 --- a/src/jackpal/androidterm/shortcuts/AddShortcut.java +++ b/src/jackpal/androidterm/shortcuts/AddShortcut.java @@ -1,7 +1,6 @@ //From the desk of Frank P. Westlake; public domain. package jackpal.androidterm.shortcuts; -import android.app. AlertDialog; import android.content. Context; import android.content. DialogInterface; import android.content. Intent; @@ -21,6 +20,7 @@ import android.widget. TextView; import android.widget. EditText; import jackpal.androidterm.R; +import jackpal.androidterm.compat.AlertDialogCompat; import java.io. File; @@ -53,7 +53,7 @@ protected void onCreate(Bundle savedInstanceState) void makeShortcut() { if(path==null) path=""; - final AlertDialog.Builder alert=new AlertDialog.Builder(context); + final AlertDialogCompat.Builder alert=new AlertDialogCompat.Builder(context, AlertDialogCompat.THEME_HOLO_DARK); LinearLayout lv=new LinearLayout(context); lv.setOrientation(LinearLayout.VERTICAL); for(int i=0, n=et.length; i Date: Fri, 30 May 2014 09:31:55 -0700 Subject: [PATCH 704/847] Improve compatability layer for API < 11. Now the dialog shows OK, but still crash when writing the Intent to a a parcel. --- .../androidterm/compat/AlertDialogCompat.java | 22 ++++++++++++++----- .../androidterm/shortcuts/AddShortcut.java | 3 ++- .../androidterm/shortcuts/ColorValue.java | 2 +- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/jackpal/androidterm/compat/AlertDialogCompat.java b/src/jackpal/androidterm/compat/AlertDialogCompat.java index 2539e1d44..28cacf81e 100644 --- a/src/jackpal/androidterm/compat/AlertDialogCompat.java +++ b/src/jackpal/androidterm/compat/AlertDialogCompat.java @@ -18,10 +18,6 @@ private AlertDialogCompat(Context context) { super(context); } - private AlertDialogCompat(Context context, int theme) - { - super(context, theme); - } private AlertDialogCompat(Context context, boolean cancelable, DialogInterface.OnCancelListener cancelListener) { super(context, cancelable, cancelListener); @@ -66,7 +62,7 @@ public static AlertDialog newInstance(Context context, int theme) { return(new Api11OrLater(context, theme)); } - return(new AlertDialogCompat(context, theme)); + return(new AlertDialogCompat(context)); } //////////////////////////////////////////////////////////// public static AlertDialog newInstance(Context context, boolean cancelable, DialogInterface.OnCancelListener cancelListener) @@ -74,4 +70,20 @@ public static AlertDialog newInstance(Context context, boolean cancelable, Dialo return(new AlertDialogCompat(context, cancelable, cancelListener)); } //////////////////////////////////////////////////////////// + + public static AlertDialog.Builder newInstanceBuilder(Context context, int theme) { + if (AndroidCompat.SDK >= 11) { + return new Api11OrLaterBuilder(context, theme); + } else { + return new AlertDialog.Builder(context); + } + } + private static class Api11OrLaterBuilder extends AlertDialog.Builder { + public Api11OrLaterBuilder(Context context) { + super(context); + } + public Api11OrLaterBuilder(Context context, int theme) { + super(context, theme); + } + } } diff --git a/src/jackpal/androidterm/shortcuts/AddShortcut.java b/src/jackpal/androidterm/shortcuts/AddShortcut.java index f2707c7c0..73801d3e7 100755 --- a/src/jackpal/androidterm/shortcuts/AddShortcut.java +++ b/src/jackpal/androidterm/shortcuts/AddShortcut.java @@ -53,7 +53,8 @@ protected void onCreate(Bundle savedInstanceState) void makeShortcut() { if(path==null) path=""; - final AlertDialogCompat.Builder alert=new AlertDialogCompat.Builder(context, AlertDialogCompat.THEME_HOLO_DARK); + final AlertDialogCompat.Builder alert = + AlertDialogCompat.newInstanceBuilder(context, AlertDialogCompat.THEME_HOLO_DARK); LinearLayout lv=new LinearLayout(context); lv.setOrientation(LinearLayout.VERTICAL); for(int i=0, n=et.length; i Date: Fri, 30 May 2014 13:37:11 -0700 Subject: [PATCH 705/847] Fix NPE on Cupcake. (Works around bug in Cupcake version of android.net.Uri parcel writing code. Hurray for open source and exception stack crawls!) --- src/jackpal/androidterm/shortcuts/AddShortcut.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/jackpal/androidterm/shortcuts/AddShortcut.java b/src/jackpal/androidterm/shortcuts/AddShortcut.java index 73801d3e7..c0eb7be36 100755 --- a/src/jackpal/androidterm/shortcuts/AddShortcut.java +++ b/src/jackpal/androidterm/shortcuts/AddShortcut.java @@ -210,7 +210,10 @@ void buildShortcut( , int shortcutColor ) { - android.net.Uri.Builder urib=new android.net.Uri.Builder().scheme("File"); + Uri.Builder urib=new Uri.Builder().scheme("file"); + // Explicitly setting these unused fields to null avoids a NPE when writing to Parcel for Android SDK level 3. + urib.authority(null).query(null).fragment(null); + if(path!=null && !path.equals("")) urib.path(path); if(arguments!=null && !arguments.equals("")) urib.fragment(arguments!=null?arguments:""); android.net.Uri uri=urib.build(); From e0f8e648529077499f63e91567f9999a33944342 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sun, 1 Jun 2014 16:19:45 -0700 Subject: [PATCH 706/847] v1.0.61 --- AndroidManifest.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 337a5266a..91ffeeba4 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,8 +1,8 @@ From fda15fe4ae9215faefae06873a542206aaf8245d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1szl=C3=B3=20G=C3=A1rdonyi?= Date: Wed, 4 Jun 2014 15:05:10 +0200 Subject: [PATCH 707/847] Updated Hungarian translation Added new string. --- res/values-hu/strings.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml index 35ced4b22..9980fb05f 100644 --- a/res/values-hu/strings.xml +++ b/res/values-hu/strings.xml @@ -183,6 +183,7 @@ Paraméterek\: Ikon címkéje\: Szöveges ikon + SZÖVEGES IKON LÉTREHOZÁSA Terminál parancsikon @@ -193,4 +194,4 @@ RÖGZÍT Adjon meg szöveget az ikonhoz - \ No newline at end of file + From 64343908b08146c0c25fd55668766c47d7aef9d0 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Wed, 4 Jun 2014 07:23:56 -0700 Subject: [PATCH 708/847] Update README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index af89bb70d..a4f99e9cf 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,8 @@ Please see the [Recent Updates](http://github.com/jackpal/Android-Terminal-Emulator/wiki/Recent-Updates) page for recent updates. -Not on Market? Don't want to compile your own version? No problem. A fairly recent version of -Android Terminal Emulator is here: +If you are unable to use the Play Store, you can download a copy of +Android Terminal Emulator here: [Download Prebuilt version for side-loading](http://jackpal.github.com/Android-Terminal-Emulator/downloads/Term.apk) + +Notice: Android Terminal Emulator development is complete. I am still accepting new language translations and bug fixes, but I am not accepting new features. See [[Wrapping up Development on Android Terminal Emulator]] for details of the wrapping-up process. From 4afd7e7fb221b19e73cfa1df92f48ac329de681e Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Wed, 4 Jun 2014 07:24:56 -0700 Subject: [PATCH 709/847] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a4f99e9cf..01a35432a 100644 --- a/README.md +++ b/README.md @@ -20,4 +20,6 @@ If you are unable to use the Play Store, you can download a copy of Android Terminal Emulator here: [Download Prebuilt version for side-loading](http://jackpal.github.com/Android-Terminal-Emulator/downloads/Term.apk) -Notice: Android Terminal Emulator development is complete. I am still accepting new language translations and bug fixes, but I am not accepting new features. See [[Wrapping up Development on Android Terminal Emulator]] for details of the wrapping-up process. +Notice: Android Terminal Emulator development is complete. I am still accepting new language translations and bug fixes, but I am not accepting new features. See [[Wrapping up Development on Android Terminal Emulator] +https://github.com/jackpal/Android-Terminal-Emulator/wiki/Wrapping-up-Development-on-Android-Terminal-Emulator] +for details of the wrapping-up process. From a7c9564c746fd86f90ec9426d668213281e9776d Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Wed, 4 Jun 2014 07:25:22 -0700 Subject: [PATCH 710/847] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 01a35432a..a91e56276 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,6 @@ If you are unable to use the Play Store, you can download a copy of Android Terminal Emulator here: [Download Prebuilt version for side-loading](http://jackpal.github.com/Android-Terminal-Emulator/downloads/Term.apk) -Notice: Android Terminal Emulator development is complete. I am still accepting new language translations and bug fixes, but I am not accepting new features. See [[Wrapping up Development on Android Terminal Emulator] -https://github.com/jackpal/Android-Terminal-Emulator/wiki/Wrapping-up-Development-on-Android-Terminal-Emulator] +Notice: Android Terminal Emulator development is complete. I am still accepting new language translations and bug fixes, but I am not accepting new features. See [Wrapping up Development on Android Terminal Emulator] +(https://github.com/jackpal/Android-Terminal-Emulator/wiki/Wrapping-up-Development-on-Android-Terminal-Emulator) for details of the wrapping-up process. From 6202a690cba58ada33e2cfa30e531ead08c61d8b Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Wed, 4 Jun 2014 07:27:37 -0700 Subject: [PATCH 711/847] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a91e56276..84758a5a4 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,10 @@ This code is based on the "Term" application which is included in the Android Op [Download the Android Terminal Emulator from Google Play](https://play.google.com/store/apps/details?id=jackpal.androidterm) +If you are unable to use the Play Store, you can also +[download from GitHub](http://jackpal.github.com/Android-Terminal-Emulator/downloads/Term.apk) + + Got questions? Please check out the [FAQ](http://github.com/jackpal/Android-Terminal-Emulator/wiki/Frequently-Asked-Questions) before emailing or adding an issue. Thanks! @@ -16,10 +20,6 @@ Please see the [Recent Updates](http://github.com/jackpal/Android-Terminal-Emulator/wiki/Recent-Updates) page for recent updates. -If you are unable to use the Play Store, you can download a copy of -Android Terminal Emulator here: -[Download Prebuilt version for side-loading](http://jackpal.github.com/Android-Terminal-Emulator/downloads/Term.apk) - Notice: Android Terminal Emulator development is complete. I am still accepting new language translations and bug fixes, but I am not accepting new features. See [Wrapping up Development on Android Terminal Emulator] (https://github.com/jackpal/Android-Terminal-Emulator/wiki/Wrapping-up-Development-on-Android-Terminal-Emulator) for details of the wrapping-up process. From c903ed96cd04b10d75f9ac1a83d08e856aa0d32c Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Sun, 4 May 2014 21:16:03 -0700 Subject: [PATCH 712/847] TermSession: add a fast path for ASCII in write(int codePoint) --- .../jackpal/androidterm/emulatorview/TermSession.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TermSession.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TermSession.java index 6555859eb..9e2c5c3b0 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TermSession.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TermSession.java @@ -290,8 +290,16 @@ public void write(String data) { * @param codePoint The Unicode code point to write to the terminal. */ public void write(int codePoint) { - CharBuffer charBuf = mWriteCharBuffer; ByteBuffer byteBuf = mWriteByteBuffer; + if (codePoint < 128) { + // Fast path for ASCII characters + byte[] buf = byteBuf.array(); + buf[0] = (byte) codePoint; + write(buf, 0, 1); + return; + } + + CharBuffer charBuf = mWriteCharBuffer; CharsetEncoder encoder = mUTF8Encoder; charBuf.clear(); From 0da354d0a255a8cc4d60a67d77c598f2b5fc478d Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Tue, 6 May 2014 03:55:28 -0700 Subject: [PATCH 713/847] Add some torture tests for Unicode support These are a set of specially-crafted text files designed to exercise most of the corner cases in the Unicode support code, particularly FullUnicodeLine.setChar()'s support for overwriting screen columns with changes in sequence lengths and display widths. For best (worst?) results, use the tests in vttest mode; the lines of exactly 80 columns will be more likely to uncover bugs that way. --- .../combiningCharReplacement.txt | 10 ++ tests/wideChars/combining-chars.txt | 57 ++++++++++ tests/wideChars/last-column-wrapping.txt | 4 + tests/wideChars/linkification.txt | 6 ++ tests/wideChars/overwriting1.txt | 16 +++ tests/wideChars/overwriting2.txt | 16 +++ tests/wideChars/overwriting3.txt | 16 +++ tests/wideChars/overwriting4.txt | 16 +++ tests/wideChars/overwriting5.txt | 16 +++ tests/wideChars/sip-chars.txt | 100 ++++++++++++++++++ 10 files changed, 257 insertions(+) create mode 100644 tests/controlSequences/combiningCharReplacement.txt create mode 100644 tests/wideChars/combining-chars.txt create mode 100644 tests/wideChars/last-column-wrapping.txt create mode 100644 tests/wideChars/linkification.txt create mode 100644 tests/wideChars/overwriting1.txt create mode 100644 tests/wideChars/overwriting2.txt create mode 100644 tests/wideChars/overwriting3.txt create mode 100644 tests/wideChars/overwriting4.txt create mode 100644 tests/wideChars/overwriting5.txt create mode 100644 tests/wideChars/sip-chars.txt diff --git a/tests/controlSequences/combiningCharReplacement.txt b/tests/controlSequences/combiningCharReplacement.txt new file mode 100644 index 000000000..16230d9ba --- /dev/null +++ b/tests/controlSequences/combiningCharReplacement.txt @@ -0,0 +1,10 @@ +Overwriting a letter with combining diacritic with a letter without in an +80-column line: + +xxxxxMu[combining diaeresis]nchenxxx[...] ESC [ 7 G u: +xxxxxMünchenxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxu + +Overwriting a letter without combining diacritic with a letter with combining diacritic in an 80-column line: + +xxxxMunchenxxx[...] ESC [ 6 G u [combining diaeresis]: +xxxxMunchenxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxü diff --git a/tests/wideChars/combining-chars.txt b/tests/wideChars/combining-chars.txt new file mode 100644 index 000000000..fb777ceff --- /dev/null +++ b/tests/wideChars/combining-chars.txt @@ -0,0 +1,57 @@ +East Asian wide characters with combining diacritics: + +中文 [combining low line]: +中文̲ + +xxx中[combining low line]文: +xxx中̲文 + +Overwriting an East Asian wide character with combining diacritic with an East +Asian wide character without combining diacritic, in the middle of an 80-column +line with mixed narrow and wide chars: + +xxx[...]xxx中文[combining low line]xxx[...]xxx你好ESC [ 23 G 国: +xxxxxxxxxxxxxxxxxxxx中文̲xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好国 + +xxx[...]xxx中文[combining low line]。xxx[...]xxx你好xESC [ 23 G 国: +xxxxxxxxxxxxxxxxxxxx中文̲。xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好x国 + +Overwriting an East Asian wide character with another one with a combining +diacritic, in the middle of an 80-column line with mixed narrow and wide chars: + +xxx[...]xxx中文xxx[...]xxx你好ESC [ 23 G 国[combining low line]: +xxxxxxxxxxxxxxxxxxxx中文xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好国̲ + +xxx[...]xxx中文。xxx[...]xxx你好xESC [ 23 G 国[combining low line]: +xxxxxxxxxxxxxxxxxxxx中文。xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好x国̲ + +Overwriting a narrow character with an East Asian wide character with a +combining diacritic, in the middle of an 80-column line with mixed narrow and +wide chars: + +xxx[...]xxx中文xxx[...]xxx你好xESC [ 25 G 。[combining low line]: +xxxxxxxxxxxxxxxxxxxx中文xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好x。̲ + +xxx[...]xxx中y文xxx[...]xxx你好ESC [ 23 G ,[combining low line]: +xxxxxxxxxxxxxxxxxxxx中y文xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好,̲ + +Overwriting a narrow character with a combining diacritic with an East Asian +wide character, in the middle of an 80-column line with mixed narrow and +widechars: + +xxxxxxxxàxxx[...]xxx中文xxx[...]xxx你好xESC [ 9 G 中: +xxxxxxxxàxxxxxxxxxxx中文xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好x中 + +xxxxxxxxàxxx[...]xxx中文xxx[...]xxx你好xESC [ 8 G 中: +xxxxxxxxàxxxxxxxxxxx中文xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好x中 + +Overwriting an East Asian wide character with combining diacritic with a narrow +character without combining diacritic, in the middle of an 80-column line with +mixed narrow and wide chars: + +xxx[...]xxx中文[combining low line]xxx[...]xxx你好ESC [ 23 G y: +xxxxxxxxxxxxxxxxxxxx中文̲xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好y + +xxx[...]xxx中文[combining low line]。xxx[...]xxx你好xESC [ 23 G y: +xxxxxxxxxxxxxxxxxxxx中文̲。xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好xy + diff --git a/tests/wideChars/last-column-wrapping.txt b/tests/wideChars/last-column-wrapping.txt new file mode 100644 index 000000000..23ad10636 --- /dev/null +++ b/tests/wideChars/last-column-wrapping.txt @@ -0,0 +1,4 @@ +A line taking 81 screen columns, with an East Asian wide character at column 79 +(should be truncated or wrapped on an 80-column terminal): + +xxxxxxxxxxxxxxxxxxxx中文xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好 diff --git a/tests/wideChars/linkification.txt b/tests/wideChars/linkification.txt new file mode 100644 index 000000000..dabd24a7c --- /dev/null +++ b/tests/wideChars/linkification.txt @@ -0,0 +1,6 @@ +A link on a line with East Asian wide characters: +中国国际航空公司 http://www.airchina.com/ + +A link on a long line with East Asian wide characters (the link should be +wrapped across two lines on an 80-column terminal): +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 中国国际航空公司 http://www.airchina.com/ diff --git a/tests/wideChars/overwriting1.txt b/tests/wideChars/overwriting1.txt new file mode 100644 index 000000000..b1fdd115a --- /dev/null +++ b/tests/wideChars/overwriting1.txt @@ -0,0 +1,16 @@ +Overwriting an East Asian wide character with another one: + +中国 ESC [ 2 D 文: +中国文 + +xxx中文 ESC [ 2 D 国: +xxx中文国 + +Overwriting an East Asian wide character with another one, in the middle of +an 80-column line with mixed narrow and wide chars: + +xxx[...]xxx中文xxx[...]xxx你好ESC [ 23 G 国: +xxxxxxxxxxxxxxxxxxxx中文xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好国 + +xxx[...]xxx中文。xxx[...]xxx你好xESC [ 23 G 国: +xxxxxxxxxxxxxxxxxxxx中文。xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好x国 diff --git a/tests/wideChars/overwriting2.txt b/tests/wideChars/overwriting2.txt new file mode 100644 index 000000000..66d438286 --- /dev/null +++ b/tests/wideChars/overwriting2.txt @@ -0,0 +1,16 @@ +Inserting an East Asian wide character into the middle of another one: + +中国 ESC [ D 文: +中国文 + +xxx中文 ESC [ D 国: +xxx中文国 + +Inserting an East Asian wide character into the middle of another one, in the +middle of an 80-column line with mixed narrow and wide chars: + +xxx[...]xxx中文xxx[...]xxx你好xESC [ 24 G 国: +xxxxxxxxxxxxxxxxxxxx中文xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好x国 + +xxx[...]xxx中文。xxx[...]xxx你好ESC [ 24 G 国: +xxxxxxxxxxxxxxxxxxxx中文。xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好国 diff --git a/tests/wideChars/overwriting3.txt b/tests/wideChars/overwriting3.txt new file mode 100644 index 000000000..93be2cc28 --- /dev/null +++ b/tests/wideChars/overwriting3.txt @@ -0,0 +1,16 @@ +Overwriting an East Asian wide character with a narrow character: + +中国 ESC [ 2 D x: +中国x + +xxx中文 ESC [ 2 D x: +xxx中文x + +Overwriting an East Asian wide character with a narrow character, in the middle +of an 80-column line with mixed narrow and wide chars: + +xxx[...]xxx中文xxx[...]xxx你好xESC [ 23 G y: +xxxxxxxxxxxxxxxxxxxx中文xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好xy + +xxx[...]xxx中文。xxx[...]xxx你好ESC [ 23 G y: +xxxxxxxxxxxxxxxxxxxx中文。xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好y diff --git a/tests/wideChars/overwriting4.txt b/tests/wideChars/overwriting4.txt new file mode 100644 index 000000000..7c3aaa286 --- /dev/null +++ b/tests/wideChars/overwriting4.txt @@ -0,0 +1,16 @@ +Overwriting a narrow character with an East Asian wide character: + +xxx ESC [ D 中: +xxx中 + +xxx ESC [ 3 D 中: +xxx中 + +Overwriting a narrow character with an East Asian wide character, in the middle +of an 80-column line with mixed narrow and wide chars: + +xxx[...]xxx中文xxx[...]xxx你好xESC [ 25 G 版: +xxxxxxxxxxxxxxxxxxxx中文xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好x版 + +xxx[...]xxx中文y。xxx[...]xxx你好ESC [ 25 G 版: +xxxxxxxxxxxxxxxxxxxx中文y。xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好版 diff --git a/tests/wideChars/overwriting5.txt b/tests/wideChars/overwriting5.txt new file mode 100644 index 000000000..670a9c42d --- /dev/null +++ b/tests/wideChars/overwriting5.txt @@ -0,0 +1,16 @@ +Inserting a narrow character into the middle of an East Asian wide character: + +中国 ESC [ D x: +中国x + +xxx中文 ESC [ D x: +xxx中文x + +Inserting a narrow character into the middle of an East Asian wide character, +in the middle of an 80-column line with mixed narrow and wide chars: + +xxx[...]xxx中文xxx[...]xxx你好xESC [ 24 G y: +xxxxxxxxxxxxxxxxxxxx中文xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好xy + +xxx[...]xxx中文。xxx[...]xxx你好ESC [ 24 G y: +xxxxxxxxxxxxxxxxxxxx中文。xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好y diff --git a/tests/wideChars/sip-chars.txt b/tests/wideChars/sip-chars.txt new file mode 100644 index 000000000..ee39100a0 --- /dev/null +++ b/tests/wideChars/sip-chars.txt @@ -0,0 +1,100 @@ +East Asian wide characters in the Supplementary Ideographic Plane (thanks to +http://www.i18nguy.com/unicode/supplementary-test.html): + +𠜎𠜱𠝹𠱓𠱸 + +Mixed East Asian wide characters from the BMP and SIP: + +中文:𠜎𠜱,你好,𠝹国。 +𠱸,中文:你好,𠝹国。 + +Narrow characters and mixed east Asian wide characters from the BMP and SIP, in +an 80-column line: +xxxxxxxxxx中文:𠜎𠜱,你好,xx𠝹国。xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好xxxxxxxxxx +xxxxxxxxxx𠱸,中文:你xx好,𠝹国。xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx好 + +Overwriting an East Asian wide character from the SIP with a narrow character: + +中文:𠜎 ESC [ 2 D x: +中文:𠜎x + +xxx中文:𠜎 ESC [ 2 D x: +xxx中文:𠜎x + +Overwriting an East Asian wide character from the SIP with a narrow character, +in the middle of an 80-column line with mixed narrow and wide chars: + +xxx[...]xxx中文:𠜎xxx[...]xxx你好xESC [ 27 G y: +xxxxxxxxxxxxxxxxxxxx中文:𠜎xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好xy + +xxx[...]xxx中文:𠜎。xxx[...]xxx你好ESC [ 27 G y: +xxxxxxxxxxxxxxxxxxxx中文:𠜎。xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好y + +Overwriting an East Asian wide character from the SIP with an East Asian wide +character from the BMP: + +中文:𠜎 ESC [ 2 D 国: +中文:𠜎国 + +xxx中文:𠜎 ESC [ 2 D 国: +xxx中文:𠜎国 + +Overwriting an East Asian wide character from the SIP with an East Asian wide +character from the BMP in the middle of an 80-column line with mixed narrow and +wide chars: + +xxx[...]xxx中文:𠜎xxx[...]xxx你好xESC [ 27 G 国: +xxxxxxxxxxxxxxxxxxxx中文:𠜎xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好x国 + +xxx[...]xxx中文:𠜎。xxx[...]xxx你好ESC [ 27 G 国: +xxxxxxxxxxxxxxxxxxxx中文:𠜎。xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好国 + +Overwriting an East Asian wide character from the BMP with an East Asian wide +character from the SIP: + +中文:国 ESC [ 2 D 𠜎: +中文:国𠜎 + +xxx中文:国 ESC [ 2 D 𠜎: +xxx中文:国𠜎 + +Overwriting an East Asian wide character from the BMP with an East Asian wide +character from the SIP in the middle of an 80-column line with mixed narrow and +wide chars: + +xxx[...]xxx中文:国xxx[...]xxx你好xESC [ 27 G 𠜎: +xxxxxxxxxxxxxxxxxxxx中文:国xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好x𠜎 + +xxx[...]xxx中文:国。xxx[...]xxx你好ESC [ 27 G 𠜎: +xxxxxxxxxxxxxxxxxxxx中文:国。xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好𠜎 + +Inserting a narrow character into the middle of an East Asian wide character +from the SIP in the middle of an 80-column line with mixed narrow and wide +chars: + +xxx[...]xxx中文:𠜎xxx[...]xxx你好xESC [ 28 G y: +xxxxxxxxxxxxxxxxxxxx中文:𠜎xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好xy + +xxx[...]xxx中文:𠜎。xxx[...]xxx你好ESC [ 28 G y: +xxxxxxxxxxxxxxxxxxxx中文:𠜎。xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好y + +Inserting an East Asian wide character from the SIP into the middle of an East +Asian wide character from the BMP in the middle of an 80-column line with mixed +narrow and wide chars: + +xxx[...]xxx中文:国xxx[...]xxx你好xESC [ 28 G 𠜎: +xxxxxxxxxxxxxxxxxxxx中文:国xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好x𠜎 + +xxx[...]xxx中文:国。xxx[...]xxx你好ESC [ 28 G 𠜎: +xxxxxxxxxxxxxxxxxxxx中文:国。xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好𠜎 + +Inserting an East Asian wide character from the BMP into the middle of an East +Asian wide character from the SIP in the middle of an 80-column line with mixed +narrow and wide chars: + +xxx[...]xxx中文:𠜎xxx[...]xxx你好xESC [ 28 G 国: +xxxxxxxxxxxxxxxxxxxx中文:𠜎xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好x国 + +xxx[...]xxx中文:𠜎。xxx[...]xxx你好ESC [ 28 G 国: +xxxxxxxxxxxxxxxxxxxx中文:𠜎。xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx你好国 + From 7335c643f758ce4351ac00813027ce1505f8dbd4 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Mon, 5 May 2014 02:52:08 -0700 Subject: [PATCH 714/847] TerminalEmulator: conform to standard on handling of invalid UTF-8 sequences The Unicode standard requires that, when dealing with ill-formed UTF-8 (version 6.2, page 96): If the converter encounters an ill-formed UTF-8 code unit sequence which starts with a valid first byte, but which does not continue with valid successor bytes [...], it must not consume the successor bytes as part of the ill-formed subsequence whenever those successor bytes themselves constitute part of a well-formed UTF-8 code unit subsequence. This implies that when we hit a byte in the input stream which cannot fit into the sequence currently being decoded, we must attempt to decode that byte again after resetting our decoder state. --- .../jackpal/androidterm/emulatorview/TerminalEmulator.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TerminalEmulator.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TerminalEmulator.java index e97a0afe6..9c830b429 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TerminalEmulator.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TerminalEmulator.java @@ -823,7 +823,11 @@ private boolean handleUTF8Sequence(byte b) { mUTF8ToFollow = 0; mUTF8ByteBuffer.clear(); emit(UNICODE_REPLACEMENT_CHAR); - return true; + + /* The Unicode standard (section 3.9, definition D93) requires + * that we now attempt to process this byte as though it were + * the beginning of another possibly-valid sequence */ + return handleUTF8Sequence(b); } mUTF8ByteBuffer.put(b); From 9a47042620bec87617f0b4f5d50568535668fe26 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Mon, 5 May 2014 05:28:34 -0700 Subject: [PATCH 715/847] Fix inserting characters into the middle of an East Asian wide char We currently assume that the column we're inserting a char into is the start of a display character, and compute the length of the subsequence of mText storing the column's contents accordingly. This breaks, however, when inserting into the second column spanned by an East Asian wide character. Detect this case and handle it specially when we need to find the start of the next independent column's contents (such as when computing the length of the existing sequence stored in this column). Also, to preserve column alignment, pad the column before with a space; if the character being inserted is a wide character, clobber the next column's contents too. Fixes the second coming of #145, and potentially other difficult-to-reproduce bugs concerning East Asian wide character support as well. --- .../emulatorview/UnicodeTranscript.java | 96 ++++++++++++++----- 1 file changed, 70 insertions(+), 26 deletions(-) diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/UnicodeTranscript.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/UnicodeTranscript.java index ca9111b64..9a9566c6c 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/UnicodeTranscript.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/UnicodeTranscript.java @@ -875,9 +875,25 @@ public void setChar(int column, int codePoint) { int charWidth = UnicodeTranscript.charWidth(codePoint); int oldCharWidth = UnicodeTranscript.charWidth(text, pos); + if (charWidth == 2 && column == columns - 1) { + // A width 2 character doesn't fit in the last column. + codePoint = ' '; + charWidth = 1; + } + + boolean wasExtraColForWideChar = false; + if (oldCharWidth == 2 && column > 0) { + /* If the previous screen column starts at the same offset in the + * array as this one, this column must be the second column used + * by an East Asian wide character */ + wasExtraColForWideChar = (findStartOfColumn(column - 1) == pos); + } + // Get the number of elements in the mText array this column uses now int oldLen; - if (column + oldCharWidth < columns) { + if (wasExtraColForWideChar && column + 1 < columns) { + oldLen = findStartOfColumn(column + 1) - pos; + } else if (column + oldCharWidth < columns) { oldLen = findStartOfColumn(column+oldCharWidth) - pos; } else { oldLen = spaceUsed - pos; @@ -927,44 +943,72 @@ public void setChar(int column, int codePoint) { } /* - * Handle cases where charWidth changes - * width 1 -> width 2: should clobber the contents of the next - * column (if next column contains wide char, need to pad with a space) - * width 2 -> width 1: pad with a space after the new character + * Handle cases where we need to pad with spaces to preserve column + * alignment + * + * width 2 -> width 1: pad with a space before or after the new + * character, depending on which of the two previously-occupied columns + * we wrote into + * + * inserting width 2 character into the second column of an existing + * width 2 character: pad with a space before the new character */ - if (oldCharWidth == 2 && charWidth == 1) { - // Pad with a space + if (oldCharWidth == 2 && charWidth == 1 || wasExtraColForWideChar && charWidth == 2) { int nextPos = pos + newLen; + char[] newText = text; if (spaceUsed + 1 > text.length) { // Array needs growing - char[] newText = new char[text.length + columns]; - System.arraycopy(text, 0, newText, 0, nextPos); + newText = new char[text.length + columns]; + System.arraycopy(text, 0, newText, 0, wasExtraColForWideChar ? pos : nextPos); + } + if (wasExtraColForWideChar) { + // Padding goes before the new character + System.arraycopy(text, pos, newText, pos + 1, spaceUsed - pos); + newText[pos] = ' '; + } else { + // Padding goes after the new character System.arraycopy(text, nextPos, newText, nextPos + 1, spaceUsed - nextPos); + newText[nextPos] = ' '; + } + + if (newText != text) { + // Update mText to point to the newly grown array mText = text = newText; - } else { - System.arraycopy(text, nextPos, text, nextPos + 1, spaceUsed - nextPos); } - text[nextPos] = ' '; // Update space used - ++offset[0]; + spaceUsed = ++offset[0]; - // Correct the offset for the next column to reflect width change - if (column == 0) { - offset[1] = (short) (newLen - 1); - } else if (column + 1 < columns) { - offset[column + 1] = (short) (offset[column] + newLen - 1); + // Correct the offset for the just-modified column to reflect + // width change + if (wasExtraColForWideChar) { + ++offset[column]; + ++pos; + } else { + if (column == 0) { + offset[1] = (short) (newLen - 1); + } else if (column + 1 < columns) { + offset[column + 1] = (short) (offset[column] + newLen - 1); + } + ++column; } - ++column; + ++shift; - } else if (oldCharWidth == 1 && charWidth == 2) { - if (column == columns - 1) { - // A width 2 character doesn't fit in the last column. - text[pos] = ' '; - offset[0] = (short) (pos + 1); - shift = 0; - } else if (column == columns - 2) { + } + + /* + * Handle cases where we need to clobber the contents of the next + * column in order to preserve column alignment + * + * width 1 -> width 2: should clobber the contents of the next + * column (if next column contains wide char, need to pad with a space) + * + * inserting width 2 character into the second column of an existing + * width 2 character: same + */ + if (oldCharWidth == 1 && charWidth == 2 || wasExtraColForWideChar && charWidth == 2) { + if (column == columns - 2) { // Correct offset for the next column to reflect width change offset[column + 1] = (short) (offset[column] - 1); From 9e02953fc32ca85b4e09e52380315180008e7cd6 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Tue, 6 May 2014 16:37:26 -0700 Subject: [PATCH 716/847] Draw combining characters at the ends of runs correctly At the moment, TranscriptScreen's drawTextRun() tracks the screen column corresponding to the character it's working on at the moment by updating the column after processing each character; this results in combining characters being considered part of the column after the one they should apply to. For most purposes, this does not matter -- Android's own text drawing routines don't care about our column count, so the combining characters will be drawn with the correct base character. However, there are corner cases where this causes problems: * Combining characters in the last screen column will be ignored entirely (after processing the base character, the column count exceeds the number of columns, so processing stops) * If the last column of a text run (sequence with the same style, or a selection range) has combining characters, they will be erroneously drawn outside the run (the run ends after the base character, so the combining characters aren't passed to the text drawing routine as part of the run) Instead, update a next-column counter after processing each character, and use it to set the column count once we hit the next spacing character. This requires us to add array bounds checking to the loop, as it's now possible to fall off the end of the array looking for the next column; that has the side effect of papering over many possible crash bugs involving line buffer corruption. --- .../androidterm/emulatorview/TranscriptScreen.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TranscriptScreen.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TranscriptScreen.java index 4da94c02d..e92594b64 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TranscriptScreen.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TranscriptScreen.java @@ -204,6 +204,7 @@ public final void drawText(int row, Canvas canvas, float x, float y, } int columns = mColumns; + int lineLen = line.length; int lastStyle = 0; boolean lastSelectionStyle = false; int runWidth = 0; @@ -211,12 +212,11 @@ public final void drawText(int row, Canvas canvas, float x, float y, int lastRunStartIndex = -1; boolean forceFlushRun = false; int column = 0; + int nextColumn = 0; int index = 0; int cursorIndex = 0; int cursorIncr = 0; - while (column < columns) { - int style = color.get(column); - boolean selectionStyle = false; + while (column < columns && index < lineLen && line[index] != '\0') { int incr = 1; int width; if (Character.isHighSurrogate(line[index])) { @@ -225,6 +225,12 @@ public final void drawText(int row, Canvas canvas, float x, float y, } else { width = UnicodeTranscript.charWidth(line[index]); } + if (width > 0) { + // We've moved on to the next column + column = nextColumn; + } + int style = color.get(column); + boolean selectionStyle = false; if (column >= selx1 && column <= selx2) { // Draw selection: selectionStyle = true; @@ -252,7 +258,7 @@ public final void drawText(int row, Canvas canvas, float x, float y, cursorWidth = width; } runWidth += width; - column += width; + nextColumn += width; index += incr; if (width > 1) { /* We cannot draw two or more East Asian wide characters in the From 09ead3a9803ddf78ae57697b85f2e858a954db4f Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Tue, 6 May 2014 16:56:21 -0700 Subject: [PATCH 717/847] Add test of combining characters at the end of a line For use in vttest mode. --- tests/controlSequences/combiningChars.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/controlSequences/combiningChars.txt b/tests/controlSequences/combiningChars.txt index 16f57ace1..9f0915cc5 100644 --- a/tests/controlSequences/combiningChars.txt +++ b/tests/controlSequences/combiningChars.txt @@ -2,3 +2,5 @@ a combining grave accent: à a combining grave accent combining low line: à̲ a combining enclosing circle backslash: à̲⃠ musical symbol g clef: 𝄞 + +a combining grave accent combining low line at the end of an 80-column line: à̲ From c8fb346b201663fea21362c90a916dcf9922e9ee Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Tue, 6 May 2014 17:15:00 -0700 Subject: [PATCH 718/847] UnicodeTranscript: add fast path for getLine() on an entire FullUnicodeLine getLine() is called frequently during screen drawing and other operations, usually with a request for the contents of an entire line. We have a fast path for this case with basic lines; something similar works for FullUnicodeLines, so let's do that too. Also, update the documentation to reflect the fact that the array returned from getLine() will not be null-terminated if the array size exactly matches the length of the requested text. --- .../emulatorview/UnicodeTranscript.java | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/UnicodeTranscript.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/UnicodeTranscript.java index 9a9566c6c..3b73dfb10 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/UnicodeTranscript.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/UnicodeTranscript.java @@ -563,9 +563,10 @@ public static int charWidth(char[] chars, int index) { * Get the contents of a line (or part of a line) of the transcript. * * The char[] array returned may be part of the internal representation - * of the line -- make a copy first if you want to modify it. The last - * character requested will be followed by a NUL; the contents of the rest - * of the array could potentially be garbage. + * of the line -- make a copy first if you want to modify it. The returned + * array may be longer than the requested portion of the transcript; in + * this case, the last character requested will be followed by a NUL, and + * the contents of the rest of the array could potentially be garbage. * * @param row The row number to get (-mActiveTranscriptRows..mScreenRows-1) * @param x1 The first screen position that's wanted @@ -602,6 +603,17 @@ public char[] getLine(int row, int x1, int x2) { // Figure out how long the array needs to be FullUnicodeLine line = (FullUnicodeLine) mLines[row]; char[] rawLine = line.getLine(); + + if (x1 == 0 && x2 == columns) { + /* We can return the raw line after ensuring it's NUL-terminated at + * the appropriate place */ + int spaceUsed = line.getSpaceUsed(); + if (spaceUsed < rawLine.length) { + rawLine[spaceUsed] = 0; + } + return rawLine; + } + x1 = line.findStartOfColumn(x1); if (x2 < columns) { x2 = line.findStartOfColumn(x2); From 120a65c17c07f41fb3cc2bd99940322da598056c Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Sun, 11 Mar 2012 20:45:35 -0700 Subject: [PATCH 719/847] Make Hangul jamo medial vowels and final consonants combining While modern Korean is typically written using precomposed syllable blocks, Unicode also provides individual conjoining Hangul syllables (jamo) which are supposed to combine to form complete syllable blocks. Since at least Android 4.1, the Android text rendering services handle conjoining jamo correctly -- but in order for a syllable block to be rendered correctly, we need to pass its components to the text renderer as a unit. The easiest way to achieve this is to treat medial vowels and final consonants as combining characters; that way, UnicodeTranscript will automatically store them in the same column along with the initial consonant, and all our text-processing code will treat the syllable block as a unit. This behavior is strictly incorrect (isolated individual medials and finals shouldn't behave as combining characters), but it's the easiest way to get conjoining jamo working in the common cases. On platforms where we don't trust Android to render conjoining jamo correctly, we instead treat medials and finals as regular spacing characters of width 2. This is also strictly incorrect (the Unicode character database says medials and finals are East Asian neutral/narrow), but it's the best match for the way Android renders the individual jamo. --- .../emulatorview/UnicodeTranscript.java | 38 +++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/UnicodeTranscript.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/UnicodeTranscript.java index 3b73dfb10..ee1c3db02 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/UnicodeTranscript.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/UnicodeTranscript.java @@ -19,6 +19,7 @@ import android.util.Log; import jackpal.androidterm.emulatorview.compat.AndroidCharacterCompat; +import jackpal.androidterm.emulatorview.compat.AndroidCompat; /** * A backing store for a TranscriptScreen. @@ -480,6 +481,16 @@ public void blockSet(int sx, int sy, int w, int h, int val, int style) { } } + /** + * Minimum API version for which we're willing to let Android try + * rendering conjoining Hangul jamo as composed syllable blocks. + * + * This appears to work on Android 4.1.2, 4.3, and 4.4 (real devices only; + * the emulator's broken for some reason), but not on 4.0.4 -- hence the + * choice of API 16 as the minimum. + */ + static final int HANGUL_CONJOINING_MIN_SDK = 16; + /** * Gives the display width of the code point in a monospace font. * @@ -489,12 +500,12 @@ public void blockSet(int sx, int sy, int w, int h, int val, int style) { * * Known issues: * - Proper support for East Asian wide characters requires API >= 8. - * - Results are incorrect for individual Hangul jamo (a syllable block - * of jamo should be one unit with width 2). This does not affect - * precomposed Hangul syllables. * - Assigning all East Asian "ambiguous" characters a width of 1 may not * be correct if Android renders those characters as wide in East Asian * context (as the Unicode standard permits). + * - Isolated Hangul conjoining medial vowels and final consonants are + * treated as combining characters (they should only be combining when + * part of a Korean syllable block). * * @param codePoint A Unicode code point. * @return The display width of the Unicode code point. @@ -519,6 +530,27 @@ public static int charWidth(int codePoint) { return 0; } + if ((codePoint >= 0x1160 && codePoint <= 0x11FF) || + (codePoint >= 0xD7B0 && codePoint <= 0xD7FF)) { + if (AndroidCompat.SDK >= HANGUL_CONJOINING_MIN_SDK) { + /* Treat Hangul jamo medial vowels and final consonants as + * combining characters with width 0 to make jamo composition + * work correctly. + * + * XXX: This is wrong for medials/finals outside a Korean + * syllable block, but there's no easy solution to that + * problem, and we may as well at least get the common case + * right. */ + return 0; + } else { + /* Older versions of Android didn't compose Hangul jamo, but + * instead rendered them as individual East Asian wide + * characters (despite Unicode defining medial vowels and final + * consonants as East Asian neutral/narrow). Treat them as + * width 2 characters to match the rendering. */ + return 2; + } + } if (Character.charCount(codePoint) == 1) { // Android's getEastAsianWidth() only works for BMP characters switch (AndroidCharacterCompat.getEastAsianWidth((char) codePoint)) { From 2b39774dc8b956e0fdc284105845f548defb9954 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Tue, 6 May 2014 21:04:37 -0700 Subject: [PATCH 720/847] Fix selecting/copying text runs bookended by East Asian wide characters Text runs beginning or ending with East Asian wide characters can currently behave strangely when selected: * An East Asian wide character immediately before the displayed selection is sometimes copied along with the highlighted text, despite not being highlighted; * An East Asian wide character at the end of the displayed selection is sometimes omitted from the copied text, despite being highlighted. Both of these problems have the same root cause: EmulatorView's reporting of selection bounds does not take the width of the underlying character into account, even though the drawing of the selection highlight does. In other words, it's possible for EmulatorView to ask for half of an East Asian wide character to be selected, but the highlight won't reflect this (either the entire character is highlighted, or none of it is). Preventing EmulatorView from reporting selection bounds like this would require making the text selection routines aware of the underlying characters they operate on (as opposed to just working from screen positions). Instead: * Have UnicodeTranscript include half-selected East Asian wide characters at the end of a selection in copied text. * Ensure that a half-selected East Asian wide character at the beginning of a selection is highlighted. * Adjust UnicodeTranscript.getLineColor() to ensure that the number of columns of color information returned in a partial line always matches the number of columns in the text that getLine() would return. * Ensure that TranscriptScreen.internalGetTranscriptText() doesn't truncate the returned text when there are half-selected East Asian wide characters at both the beginning and end of the selection range. This requires us to avoid bounds checking based on selection width (the naive computation based on screen positions would be off by two, omitting the last character, and we lack the information here to do better); using a try-catch clause to replace the bounds check is likely to paper over some crash bugs involving line buffer corruption. This ensures a consistent user experience: half-selected East Asian wide characters are always highlighted and always included in the copied text, whether at the beginning or end of the selection. --- .../emulatorview/TranscriptScreen.java | 22 +++++-- .../emulatorview/UnicodeTranscript.java | 57 +++++++++++++++---- 2 files changed, 63 insertions(+), 16 deletions(-) diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TranscriptScreen.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TranscriptScreen.java index e92594b64..9ff63954a 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TranscriptScreen.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TranscriptScreen.java @@ -213,6 +213,7 @@ public final void drawText(int row, Canvas canvas, float x, float y, boolean forceFlushRun = false; int column = 0; int nextColumn = 0; + int displayCharWidth = 0; int index = 0; int cursorIndex = 0; int cursorIncr = 0; @@ -228,10 +229,12 @@ public final void drawText(int row, Canvas canvas, float x, float y, if (width > 0) { // We've moved on to the next column column = nextColumn; + displayCharWidth = width; } int style = color.get(column); boolean selectionStyle = false; - if (column >= selx1 && column <= selx2) { + if ((column >= selx1 || (displayCharWidth == 2 && column == selx1 - 1)) && + column <= selx2) { // Draw selection: selectionStyle = true; } @@ -363,13 +366,24 @@ private String internalGetTranscriptText(GrowableIntArray colors, int selX1, int int lastPrintingChar = -1; int lineLen = line.length; int i; - int width = x2 - x1; int column = 0; - for (i = 0; i < lineLen && column < width; ++i) { + for (i = 0; i < lineLen; ++i) { char c = line[i]; if (c == 0) { break; - } else if (c != ' ' || ((rowColorBuffer != null) && (rowColorBuffer.get(column) != defaultColor))) { + } + + int style = defaultColor; + try { + if (rowColorBuffer != null) { + style = rowColorBuffer.get(column); + } + } catch (ArrayIndexOutOfBoundsException e) { + // XXX This probably shouldn't happen ... + style = defaultColor; + } + + if (c != ' ' || style != defaultColor) { lastPrintingChar = i; } if (!Character.isLowSurrogate(c)) { diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/UnicodeTranscript.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/UnicodeTranscript.java index ee1c3db02..2b8fdfcbe 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/UnicodeTranscript.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/UnicodeTranscript.java @@ -387,7 +387,7 @@ public void blockCopy(int sx, int sy, int w, int h, int dx, int dy) { } else { // XXX There has to be a faster way to do this ... int extDstRow = dy + y; - char[] tmp = getLine(sy + y, sx, sx + w); + char[] tmp = getLine(sy + y, sx, sx + w, true); if (tmp == null) { // Source line was blank blockSet(dx, extDstRow, w, 1, ' ', mDefaultStyle); @@ -425,7 +425,7 @@ public void blockCopy(int sx, int sy, int w, int h, int dx, int dy) { System.arraycopy(lines[srcRow], sx, lines[dstRow], dx, w); } else { int extDstRow = dy + y2; - char[] tmp = getLine(sy + y2, sx, sx + w); + char[] tmp = getLine(sy + y2, sx, sx + w, true); if (tmp == null) { // Source line was blank blockSet(dx, extDstRow, w, 1, ' ', mDefaultStyle); @@ -606,6 +606,17 @@ public static int charWidth(char[] chars, int index) { * @return A char[] array containing the requested contents */ public char[] getLine(int row, int x1, int x2) { + return getLine(row, x1, x2, false); + } + + /** + * Get the whole contents of a line of the transcript. + */ + public char[] getLine(int row) { + return getLine(row, 0, mColumns, true); + } + + private char[] getLine(int row, int x1, int x2, boolean strictBounds) { if (row < -mActiveTranscriptRows || row > mScreenRows-1) { throw new IllegalArgumentException(); } @@ -648,7 +659,15 @@ public char[] getLine(int row, int x1, int x2) { x1 = line.findStartOfColumn(x1); if (x2 < columns) { - x2 = line.findStartOfColumn(x2); + int endCol = x2; + x2 = line.findStartOfColumn(endCol); + if (!strictBounds && endCol > 0 && endCol < columns - 1) { + /* If the end column is the middle of an East Asian wide + * character, include that character in the bounds */ + if (x2 == line.findStartOfColumn(endCol - 1)) { + x2 = line.findStartOfColumn(endCol + 1); + } + } } else { x2 = line.getSpaceUsed(); } @@ -662,16 +681,20 @@ public char[] getLine(int row, int x1, int x2) { return tmpLine; } - public char[] getLine(int row) { - return getLine(row, 0, mColumns); - } - /** * Get color/formatting information for a particular line. * The returned object may be a pointer to a temporary buffer, only good * until the next call to getLineColor. */ public StyleRow getLineColor(int row, int x1, int x2) { + return getLineColor(row, x1, x2, false); + } + + public StyleRow getLineColor(int row) { + return getLineColor(row, 0, mColumns, true); + } + + private StyleRow getLineColor(int row, int x1, int x2, boolean strictBounds) { if (row < -mActiveTranscriptRows || row > mScreenRows-1) { throw new IllegalArgumentException(); } @@ -680,7 +703,21 @@ public StyleRow getLineColor(int row, int x1, int x2) { StyleRow color = mColor[row]; StyleRow tmp = tmpColor; if (color != null) { - if (x1 == 0 && x2 == mColumns) { + int columns = mColumns; + if (!strictBounds && mLines[row] != null && + mLines[row] instanceof FullUnicodeLine) { + FullUnicodeLine line = (FullUnicodeLine) mLines[row]; + /* If either the start or the end column is in the middle of + * an East Asian wide character, include the appropriate column + * of style information */ + if (x1 > 0 && line.findStartOfColumn(x1-1) == line.findStartOfColumn(x1)) { + --x1; + } + if (x2 < columns - 1 && line.findStartOfColumn(x2+1) == line.findStartOfColumn(x2)) { + ++x2; + } + } + if (x1 == 0 && x2 == columns) { return color; } color.copy(x1, tmp, 0, x2-x1); @@ -690,10 +727,6 @@ public StyleRow getLineColor(int row, int x1, int x2) { } } - public StyleRow getLineColor(int row) { - return getLineColor(row, 0, mColumns); - } - boolean isBasicLine(int row) { if (row < -mActiveTranscriptRows || row > mScreenRows-1) { throw new IllegalArgumentException(); From a2b2af18bb476911a7bb88f831e683941c3c18e8 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Sat, 7 Jun 2014 21:44:51 -0700 Subject: [PATCH 721/847] Fix text drawing on lines with wide chars or combining chars at cursor * PaintRenderer.drawTextRun(): compute cursor visibility solely in units of screen position instead of mixing screen positions and indexes into the line buffer; in a long run of East Asian wide characters, this ensures correct placement of text after the cursor and prevents the cursor from disappearing midway through the line. * PaintRenderer.drawTextRun(): do not assume the display character under the cursor contains only one UTF-16 code unit; this fixes rendering of non-BMP characters and combining diacritics at the cursor. * TranscriptScreen.drawText(): make cursorIncr reflect all code units at the cursor position, including combining diacritics. --- .../androidterm/emulatorview/PaintRenderer.java | 13 +++++++------ .../androidterm/emulatorview/TranscriptScreen.java | 11 ++++++++--- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/PaintRenderer.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/PaintRenderer.java index 9593c0cf2..4b2137a34 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/PaintRenderer.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/PaintRenderer.java @@ -67,10 +67,11 @@ public void drawTextRun(Canvas canvas, float x, float y, int lineOffset, left + runWidth * mCharWidth, y, mTextPaint); - boolean cursorVisible = index <= cursorOffset && cursorOffset < (index + count); + boolean cursorVisible = lineOffset <= cursorOffset && cursorOffset < (lineOffset + runWidth); + float cursorX = 0; if (cursorVisible) { - int cursorX = (int) (x + cursorOffset * mCharWidth); - drawCursorImp(canvas, cursorX, y, cursorWidth * mCharWidth, mCharHeight, cursorMode); + cursorX = x + cursorOffset * mCharWidth; + drawCursorImp(canvas, (int) cursorX, y, cursorWidth * mCharWidth, mCharHeight, cursorMode); } boolean invisible = (effect & TextStyle.fxInvisible) != 0; @@ -97,19 +98,19 @@ public void drawTextRun(Canvas canvas, float x, float y, int lineOffset, if (cursorVisible) { // Text before cursor int countBeforeCursor = cursorIndex - index; - int countAfterCursor = count - (countBeforeCursor + 1); + int countAfterCursor = count - (countBeforeCursor + cursorIncr); if (countBeforeCursor > 0){ canvas.drawText(text, index, countBeforeCursor, left, textOriginY, mTextPaint); } // Text at cursor mTextPaint.setColor(mPalette[TextStyle.ciCursorForeground]); - canvas.drawText(text, cursorIndex, cursorIncr, x + cursorOffset * mCharWidth, + canvas.drawText(text, cursorIndex, cursorIncr, cursorX, textOriginY, mTextPaint); // Text after cursor if (countAfterCursor > 0) { mTextPaint.setColor(textPaintColor); canvas.drawText(text, cursorIndex + cursorIncr, countAfterCursor, - x + (cursorOffset + cursorWidth) * mCharWidth, + cursorX + cursorWidth * mCharWidth, textOriginY, mTextPaint); } } else { diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TranscriptScreen.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TranscriptScreen.java index 9ff63954a..e1dc3c02b 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TranscriptScreen.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TranscriptScreen.java @@ -256,9 +256,14 @@ public final void drawText(int row, Canvas canvas, float x, float y, forceFlushRun = false; } if (cx == column) { - cursorIndex = index; - cursorIncr = incr; - cursorWidth = width; + if (width > 0) { + cursorIndex = index; + cursorIncr = incr; + cursorWidth = width; + } else { + // Combining char attaching to the char under the cursor + cursorIncr += incr; + } } runWidth += width; nextColumn += width; From e2b53930eec4fa51e271b130b433e80066d63346 Mon Sep 17 00:00:00 2001 From: FrankWestlake Date: Mon, 9 Jun 2014 08:48:59 -0700 Subject: [PATCH 722/847] Allow external file picker --- AndroidManifest.xml | 3 ++- .../androidterm/shortcuts/AddShortcut.java | 19 ++++++++++++------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 337a5266a..ab3326b53 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -109,7 +109,8 @@ android:configChanges="orientation|keyboardHidden" > - + + Date: Mon, 9 Jun 2014 14:28:21 -0700 Subject: [PATCH 723/847] Allow external file picker --- AndroidManifest.xml | 9 ++++++++- src/jackpal/androidterm/shortcuts/AddShortcut.java | 4 +++- src/jackpal/androidterm/shortcuts/FSNavigator.java | 4 +++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index ab3326b53..028d65a9e 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -107,10 +107,17 @@ android:label="FSNavigator" android:launchMode="singleTop" android:configChanges="orientation|keyboardHidden" + android:exported="true" > - + + + + + + + Date: Tue, 10 Jun 2014 07:23:54 -0700 Subject: [PATCH 724/847] Allow external file picker --- src/jackpal/androidterm/shortcuts/AddShortcut.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jackpal/androidterm/shortcuts/AddShortcut.java b/src/jackpal/androidterm/shortcuts/AddShortcut.java index 12f218698..befae1576 100755 --- a/src/jackpal/androidterm/shortcuts/AddShortcut.java +++ b/src/jackpal/androidterm/shortcuts/AddShortcut.java @@ -101,7 +101,7 @@ public void onClick(View p1) else { pickerIntent - .putExtra("CONTENT_TYPE", "text/*") + .putExtra("CONTENT_TYPE", "*/*") .setAction(Intent.ACTION_PICK); } startActivityForResult(pickerIntent, OP_MAKE_SHORTCUT); From 5cf4396d5df7db7203524896a272b6ec4134aa62 Mon Sep 17 00:00:00 2001 From: Steven Luo Date: Sat, 14 Jun 2014 22:41:39 -0700 Subject: [PATCH 725/847] EmulatorView: don't crash when link ends on the last column in a line When linkifying, endRow/endCol currently represent the screen position immediately after the last character in the link; this fits naturally with Java subsequence APIs (where the end index is always one after the last position in the subsequence), but results in endRow being too large if the link in question ends on the last column of a line. Instead, have endRow/endCol point to the last character of the link, avoiding this problem (and fixing an off-by-one error in the FullUnicodeLine case, where we were already calculating endRow/endCol this way). Fixes #341. --- .../androidterm/emulatorview/EmulatorView.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/EmulatorView.java b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/EmulatorView.java index 2c389b0ae..ba192f90e 100644 --- a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/EmulatorView.java +++ b/libraries/emulatorview/src/jackpal/androidterm/emulatorview/EmulatorView.java @@ -358,11 +358,14 @@ private int createLinks(int row) int endRow; int endCol; if (textIsBasic) { + /* endRow/endCol must be the last character of the link, + * not one after -- otherwise endRow might be too large */ + int spanLastPos = spanEnd - 1; // Basic line -- can assume one char per column startRow = spanStart / mColumns; startCol = spanStart % mColumns; - endRow = spanEnd / mColumns; - endCol = spanEnd % mColumns; + endRow = spanLastPos / mColumns; + endCol = spanLastPos % mColumns; } else { /* Iterate over the line to get starting and ending columns * for this span */ @@ -403,9 +406,9 @@ private int createLinks(int row) for(int i=startRow; i <= endRow; ++i) { int runStart = (i == startRow) ? startCol: 0; - int runEnd = (i == endRow) ? endCol : mColumns; + int runEnd = (i == endRow) ? endCol : mColumns - 1; - Arrays.fill(linkRows[i], runStart, runEnd, url); + Arrays.fill(linkRows[i], runStart, runEnd + 1, url); } } From 5617972b0ee039c4671fb14f5fb53100f4edae0c Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Fri, 20 Jun 2014 08:57:19 -0700 Subject: [PATCH 726/847] 1.0.62 --- AndroidManifest.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 2ea6d42c9..b493f19d9 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,8 +1,8 @@ From 5c2683a46365ad3f8403008022ff6596f59bedae Mon Sep 17 00:00:00 2001 From: pyler Date: Sat, 21 Jun 2014 12:02:49 +0200 Subject: [PATCH 727/847] Change FSNavigator name --- AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index b493f19d9..cc389fe3f 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -104,7 +104,7 @@ Date: Mon, 30 Jun 2014 15:03:23 +0200 Subject: [PATCH 728/847] Improved Slovak translation --- res/values-sk/arrays.xml | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/res/values-sk/arrays.xml b/res/values-sk/arrays.xml index 54fe3759b..ecf9dcafe 100644 --- a/res/values-sk/arrays.xml +++ b/res/values-sk/arrays.xml @@ -22,7 +22,6 @@ - Vždy zobraziť panel akcií Skryť panel akcií (pre zobrazenie sa dotknite vrchu obrazovky alebo tlačidla Ponuka) @@ -91,24 +90,24 @@ Gulička - Klávesa \@ - Klávesa Ľavý Alt - Klávesa Pravý Alt - Klávesa Hlasitosť+ - Klávesa Hlasitosť- - Klávesa Fotoaparát - Žiadna + Kláves \@ + Kláves Ľavý Alt + Kláves Pravý Alt + Kláves Hlasitosť+ + Kláves Hlasitosť- + Kláves Fotoaparát + Žiadny Gulička - Klávesa \@ - Klávesa Ľavý Alt - Klávesa Pravý Alt - Klávesa Hlasitosť+ - Klávesa Hlasitosť- - Klávesa Fotoaparát - Žiadna + Kláves \@ + Kláves Ľavý Alt + Kláves Pravý Alt + Kláves Hlasitosť+ + Kláves Hlasitosť- + Kláves Fotoaparát + Žiadny @@ -125,7 +124,7 @@ Hlasitosť+ Hlasitosť- Fotoaparát - Žiadna + Žiadny @@ -137,6 +136,6 @@ Hlasitosť+ Hlasitosť- Fotoaparát - Žiadna + Žiadny From a13b7d602163056b75ebc59afc1a59f3a1030493 Mon Sep 17 00:00:00 2001 From: pyler Date: Mon, 30 Jun 2014 15:12:04 +0200 Subject: [PATCH 729/847] Improved Slovak translation --- res/values-sk/strings.xml | 40 +++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml index ee813c4aa..1aa6aff65 100644 --- a/res/values-sk/strings.xml +++ b/res/values-sk/strings.xml @@ -31,15 +31,15 @@ Zabrániť uspaniu Povoliť uspanie - Zabrániť odpojeniu WiFi - Povoliť odpojenie WiFi + Zabrániť odpojeniu Wi-Fi + Povoliť odpojenie Wi-Fi Upraviť text Vybrať text Kopírovať všetko Prilepiť - Poslať ovládaciu klávesu - Poslať funkčnú klávesu + Poslať ovládací kláves + Poslať funkčný kláves Okno %1$d @@ -89,13 +89,13 @@ Vybrať správanie tlačidla Späť. Správanie tlačidla Späť - Ovládacia klávesa - Vybrať ovládaciu klávesu. - Ovládacia klávesa + Ovládací kláves + Vybrať ovládací kláves. + Ovládací kláves - Funkčná klávesa - Vybrať funkčnú klávesu. - Funkčná klávesa + Funkčný kláves + Vybrať funkčný kláves. + Funkčný kláves Metóda vstupu Vybrať metódu zadávania pre softvérovú klávesnicu. @@ -107,7 +107,7 @@ Konzola Počiatočný príkaz - Odoslať do konzoly po spustení. + Poslať do konzoly po spustení. Počiatočný príkaz Typ terminálu @@ -145,15 +145,15 @@ Umožňuje aplikáciám prepísať existujúce príkazy so svojimi vlastnými verziami (podradiť priečinky v PATH) v Emulátori terminálu Android. Prepis z Emulátora terminálu Android - Odoslať prepis prostredníctvom - Nemožno vybrať e-mailovú aktivitu na odoslanie prepisu. + Poslať prepis prostredníctvom + Nemožno vybrať e-mailovú aktivitu na poslanie prepisu. - Odosielať ESC pomocou klávesy Alt - Klávesa Alt odosiela ESC. - Klávesa Alt neodosiela ESC. + Posielať ESC pomocou klávesu Alt + Kláves Alt posiela ESC. + Kláves Alt neposiela ESC. - Odosielať udalosti myši - Odosielať udalosti kliknutia a rolovania ako sekvencie ESCAPE do terminálu. + Posielať udalosti myši + Posielať udalosti kliknutia a posunu ako sekvencie ESCAPE do terminálu. Používať klávesové skratky Ctrl-Tab: prepnúť okno, Ctrl-Shift-N: nové okno, Ctrl-Shift-V: prilepiť. @@ -177,7 +177,7 @@ Argumenty\: Názov skratky\: Text ikony - VYTVORIŤ TEXT IKONY + Vytvoriť text ikony Skratka terminálu @@ -185,6 +185,6 @@ Č Z M - ZAMKNÚŤ + Zamknúť Zadajte text ikony From 750543aef168aad0ef6e2ea943a35f3a9818bfe9 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sun, 17 Aug 2014 16:45:24 -0700 Subject: [PATCH 730/847] Improved Dutch translation Courtesy Eddy Witkamp --- res/values-nl/arrays.xml | 33 +++++++----- res/values-nl/strings.xml | 110 ++++++++++++++++++++++++++++++++------ 2 files changed, 114 insertions(+), 29 deletions(-) diff --git a/res/values-nl/arrays.xml b/res/values-nl/arrays.xml index ed35f570b..b55394e35 100644 --- a/res/values-nl/arrays.xml +++ b/res/values-nl/arrays.xml @@ -22,14 +22,19 @@ - - Always show action bar - Hide action bar (touch top of screen or Menu key to show) + Menubalk altijd tonen + Menubalk verbergen (op bovenkant scherm of menuknop tikken om te tonen) + + + + Automatisch + Liggend + Staand - Geen blinkende cursor - Blinkende cursor + Geen knipperende cursor + Knipperende cursor @@ -69,18 +74,18 @@ Groene tekst op zwart Oranje tekst op zwart Rode tekst op zwart - Holo blauw tekst op zwart - Solarized Light - Solarized Dark - Linux Console + Holo-blauwe tekst op zwart + Gesolariseerd licht + Gesolariseerd donker + Linux console - Closes all terminal windows - Closes this terminal window only - Closes activity, leaving sessions running - Sends ESC to terminal - Sends TAB to terminal + Alle terminalvensters sluiten + Alleen huidige terminalvensters sluiten + App sluiten, sessies open laten + ESC naar terminal sturen + TAB naar terminal sturen diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml index 19fa3d07d..38d2cc41a 100644 --- a/res/values-nl/strings.xml +++ b/res/values-nl/strings.xml @@ -29,8 +29,8 @@ Slaapblokkering aan Slaapblokkering uit - Wi-Fi-blokkering aan - Wi-Fi-blokkering uit + Wifi-blokkering aan + Wifi-blokkering uit Tekst wijzigen Selecteer tekst @@ -43,49 +43,129 @@ Scherm Statusbalk - Statusbalk tonen/verbergen. + Statusbalk tonen/verbergen Statusbalk + Menubalk + Gedrag van menubalk kiezen (Android 3.0 en hoger) + Gedrag menubalk + + Oriëntatie van het scherm + Oriëntatie van het scherm kiezen + Oriëntatie van het scherm + Cursorstijl - Kies cursorstijl. + Cursorstijl kiezen Cursorstijl Cursorblink - Kies cursorblink. + Cursorblink kiezen Cursorblink Tekst + UTF-8 + UTF-8 standaard inschakelen + Lettergrootte - Kies karakterhoogte in punten. + Karakterhoogte in punten kiezen Lettergrootte Kleuren - Kies tekstkleur. + Tekstkleur kiezen Tekstkleur Toetsenbord - Bedieningsknop - Kies bedieningsknop. - Bedieningsknop + Terugknop + Bepalen wat de terugknop doet + Gedrag terugknop + + Control-knop + Control-knop kiezen + Control-knop Fn-knop - Kies Fn-knop. + Functieknop kiezen Fn-knop Invoermethode - Kies invoermethode voor toetsenbord. + Invoermethode voor toetsenbord kiezen Invoermethode Shell Opdrachtregel - Geef opdrachtregel-shell op + Opdrachtregel-shell opgeven Shell Beginopdracht - Wordt naar de shell verzonden bij het opstarten. + Wordt naar de shell verzonden bij opstarten Beginopdracht - Bedienings- en functieknoppen + Soort terminal + Welk soort terminal te melden aan de shell + Soort terminal + + Venster sluiten bij exit + Venster sluiten als de shell afgesloten wordt + + PATH entries controleren + Controleren of ontoegankelijk directories verwijderd moeten worden uit het PATH + + PATH-extensies toestaan + Andere apps toestaan om extra opdrachten te verstrekken (aan PATH toevoegen) + + PATH prepend toestaan + Andere apps toestaan om bestaande opdrachten te overschrijven (voor PATH invoegen). + + HOME folder + Pad naar een beschrijfbare map te gebruiken als HOME + + Control- en functieknoppen + + CTRLKEY spatie : Control-@ (NUL)\nCTRLKEY A..Z : Control-A..Z\nCTRLKEY 5 : Control-]\nCTRLKEY 6 : Control-^\nCTRLKEY 7 : Control-_\nCTRLKEY 9 : F11\nCTRLKEY 0 : F12 + Geen controlknop ingesteld + + FNKEY 1..9 : F1-F9\nFNKEY 0 : F10\nFNKEY W : Omhoog\nFNKEY A : Links\nFNKEY S : Omlaag\nFNKEY D : Rechts\nFNKEY P : PageUp\nFNKEY N : PageDown\nFNKEY T : Tab\nFNKEY L : | (pijpje)\nFNKEY U : _ (underscore)\nFNKEY E : Control-[ (ESC)\nFNKEY X : Delete\nFNKEY I : Insert\nFNKEY H : Home\nFNKEY F : End\nFNKEY . : Control-\\\n + Geen functieknop ingesteld + + Dit venster sluiten? + + Gegevens uit Android Terminal Emulator + Gegevens e-mailen via: + Kon geen e-mail activity kiezen om gegevens te sturen + + Alt-knop stuurt ESC + Alt-knop stuurt ESC + Alt-knop stuurt geen ESC + + Muisgebeurtenissen sturen + Tikken en scrollen naar de terminal sturen als \'escape sequences\' + + Sneltoesten gebruiken + Ctrl-Tab: venster wisselen, Ctrl-Shift-N: nieuw venster, Ctrl-Shift-V: plakken + Sneltoesten zijn uitgeschakeld + + Terminal + + "Terminal snelkoppeling" + + BESTANDSKIEZER + Externe opslag niet beschikbaar + Of voer hier pad in + Thema veranderen + + opdracht + --voorbeeld=\""a\" + Opdracht vinden + DOEL SNELKOPPELING KIEZEN + Opdrachtvenster vereist volledig pad, zonder toevoegingen. Voor extra opdrachten, gebruikt het veld \'Toevoeging\' (bijv: cd /sdcard). + Toevoeging\: + Naam snelkoppeling\: + Tekstpictogram + TEKSTPICTOGRAM MAKEN + Terminal snelkoppeling + + VAST + Pictogramtekst From 652ff43437d4a5c89e7459bf2b4abc28badde179 Mon Sep 17 00:00:00 2001 From: sudosurootdev Date: Mon, 18 Aug 2014 23:38:34 -0400 Subject: [PATCH 731/847] Fix Perms && Make JNI manditory module for AndroidTerm --- Android.mk | 2 +- src/jackpal/androidterm/Term.java | 0 src/jackpal/androidterm/shortcuts/AddShortcut.java | 0 src/jackpal/androidterm/shortcuts/ColorValue.java | 0 src/jackpal/androidterm/shortcuts/FSNavigator.java | 0 src/jackpal/androidterm/shortcuts/TextIcon.java | 0 6 files changed, 1 insertion(+), 1 deletion(-) mode change 100755 => 100644 src/jackpal/androidterm/Term.java mode change 100755 => 100644 src/jackpal/androidterm/shortcuts/AddShortcut.java mode change 100755 => 100644 src/jackpal/androidterm/shortcuts/ColorValue.java mode change 100755 => 100644 src/jackpal/androidterm/shortcuts/FSNavigator.java mode change 100755 => 100644 src/jackpal/androidterm/shortcuts/TextIcon.java diff --git a/Android.mk b/Android.mk index ca836d859..073d61122 100644 --- a/Android.mk +++ b/Android.mk @@ -30,7 +30,7 @@ LOCAL_PACKAGE_NAME := AndroidTerm LOCAL_MODULE_TAGS := optional -LOCAL_JNI_SHARED_LIBRARIES := libjackpal-androidterm4 +LOCAL_REQUIRED_MODULES := libjackpal-androidterm4 include $(BUILD_PACKAGE) diff --git a/src/jackpal/androidterm/Term.java b/src/jackpal/androidterm/Term.java old mode 100755 new mode 100644 diff --git a/src/jackpal/androidterm/shortcuts/AddShortcut.java b/src/jackpal/androidterm/shortcuts/AddShortcut.java old mode 100755 new mode 100644 diff --git a/src/jackpal/androidterm/shortcuts/ColorValue.java b/src/jackpal/androidterm/shortcuts/ColorValue.java old mode 100755 new mode 100644 diff --git a/src/jackpal/androidterm/shortcuts/FSNavigator.java b/src/jackpal/androidterm/shortcuts/FSNavigator.java old mode 100755 new mode 100644 diff --git a/src/jackpal/androidterm/shortcuts/TextIcon.java b/src/jackpal/androidterm/shortcuts/TextIcon.java old mode 100755 new mode 100644 From d914ae8a96a69ff6a6ad59a9a8eef20fbd7b8768 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mladen=20Pejakovi=C4=87?= Date: Thu, 25 Sep 2014 13:59:53 +0200 Subject: [PATCH 732/847] [Translations] Add Serbian --- res/values-sr/arrays.xml | 142 +++++++++++++++++++++++++++ res/values-sr/strings.xml | 197 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 339 insertions(+) create mode 100644 res/values-sr/arrays.xml create mode 100644 res/values-sr/strings.xml diff --git a/res/values-sr/arrays.xml b/res/values-sr/arrays.xml new file mode 100644 index 000000000..e9c29f42a --- /dev/null +++ b/res/values-sr/arrays.xml @@ -0,0 +1,142 @@ + + + + + + Прикажи траку стања + Сакриј траку стања + + + + + Увек прикажи траку радњи + Сакриј траку радњи (за приказ додирните врх екрана или Мени тастер) + + + + Аутоматски + Пејзаж + Портрет + + + + Статични курсор + Трепћући курсор + + + + Правоугаоник + Подвлака + Вертикална трака + + + + 4 x 8 px + 6 pt + 7 pt + 8 pt + 9 pt + 10 pt + 12 pt + 14 pt + 16 pt + 20 pt + 24 pt + 28 pt + 32 pt + 36 pt + 42 pt + 48 pt + 64 pt + 72 pt + 96 pt + 144 pt + 288 pt + + + + Црни текст на белом + Бели текст на црном + Бели текст на плавом + Зелени текст на црном + Јантарни текст на црном + Црвени текст на црном + Холо плави текст на црном + Светла соларизована + Тамна соларизована + Линукс конзола + + + + Затвара све прозоре терминала + Затвара само овај прозор терминала + Затвара активност, оставља сесије покренуте + Шаље ESC терминалу + Шаље TAB терминалу + + + + Џог куглица + \@ тастер + Леви Алт тастер + Десни Алт тастер + Звук Горе тастер + Звук Доле тастер + Камера тастер + Ниједан + + + + Џог куглица + \@ тастер + Леви Алт тастер + Десни Алт тастер + Звук Горе тастер + Звук Доле тастер + Камера тастер + Ниједан + + + + Заснована на знаковима + Заснована на речима + + + + + Куглица + \@ + Леви-Алт + Десни-Алт + Звук-Горе + Звук-Доле + Камера + Ниједан + + + + + Куглица + \@ + Леви-Алт + Десни-Алт + Звук-Горе + Звук-Доле + Камера + Ниједан + + diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml new file mode 100644 index 000000000..1eb538249 --- /dev/null +++ b/res/values-sr/strings.xml @@ -0,0 +1,197 @@ + + + + Емулатор терминала + Поставке + Нови прозор + Затвори прозор + Прозори + Прет. прозор + След. прозор + Поново постави терм + Пошаљи е-поштом + Посебни тастери + Пребаци софтверску тастатуру + + Стање терминала овог прозора је поново постављено. + + Узми блокаду буђења + Отпусти блокаду буђења + Узми блокаду бежичног + Отпусти блокаду бежичног + + Уреди текст + Изабери текст + Копирај све + Налепи + Пошаљи Цтрл унос + Пошаљи Фн унос + + Прозор %1$d + + Сесија терминала је у току + + Сесија терминала је завршена + + + Екран + + Трака стања + Прикажи/сакриј траку стања. + Трака стања + + Трака радњи + Одредите понашање траке радњи (Андроид 3.0 и новији). + Понашање траке радњи + + Оријентација екрана + Одредите понашање оријентације екрана. + Оријентација екрана + + Стил курсора + Изаберите стил курсора. + Стил курсора + + Трептање курсора + Одредите трептање курсора. + Трептање курсора + + Текст + + Подразумевај УТФ-8 + Да ли је УТФ-8 режим подразумевано омогућен. + + Величина фонта + Одредите величину фонта у тачкама. + Величина фонта + + Боје + Изаберите боју текста. + Боја текста + + Тастатура + + Понашање дугмета за назад + Шта ће да деси притиском на дугме за назад. + Понашање дугмета за назад + + Контрол тастер + Изаберите контрол (Цтрл) тастер. + Контрол тастер + + Фн тастер + Изаберите функцијски (Фн) тастер. + Фн тастер + + Метода уноса + Изаберите методу уноса за софтверску тастатуру. + Метода уноса + + Шкољка + Командна линија + Одредите командну линију шкољке. + Шкољка + + Почетна наредба + Наредба послата шкољки по покретању. + Почетна наредба + + Тип терминала + Који тип терминала да буде пријављен шкољки. + Тип терминала + + Затвори прозор по излазу + Да ли да прозор буде затворен по излазу из његове шкољке. + + Овери PATH уносе + Да ли да недоступни директоријуми буду уклољени из PATH. + + Дозволи PATH проширења + Да ли осталим апликацијама дозволити додавање додатне наредбе (да додају у PATH). + + Дозволи PATH префиксе + Да ли осталим апликацијама дозволити премошћавање постојеће наредбе (да додају на почетку PATH). + + Домаћа фасцикла + Путања до уписиве фасцикле која ће бити коришћена као HOME. + + Контролни и функцијски тастери + + CTRLKEY Размак : Control-@ (NUL)\nCTRLKEY A..Z : Control-A..Z\nCTRLKEY 5 : Control-]\nCTRLKEY 6 : Control-^\nCTRLKEY 7 : Control-_\nCTRLKEY 9 : F11\nCTRLKEY 0 : F12 + Контролни тастер није постављен. + + FNKEY 1..9 : F1-F9\nFNKEY 0 : F10\nFNKEY W : Горе\nFNKEY A : Лево\nFNKEY S : Доле\nFNKEY D : Десно\nFNKEY P : СтрГоре\nFNKEY N : СтрДоле\nFNKEY T : Таб\nFNKEY L : | (цев)\nFNKEY U : _ (подвлака)\nFNKEY E : Control-[ (ESC)\nFNKEY X : Обриши\nFNKEY I : Уметни\nFNKEY H : Почетак\nFNKEY F : Крај\nFNKEY . : Control-\\\n + Функцијски тастер није постављен. + + Затворити овај прозор? + + Покретање произвољних наредби у Емулатору Терминала + Омогућава апликацији да отвара нове прозоре у Емулатору Терминала за Андроид и покреће наредбе у тим прозорима са свим дозволама Емулатора Терминала, укључујући приступ интернету и вашој СД картици. + Додавање наредби у Емулатор Терминала + Дозвољава апликацији да пружи додатне наредбе (додавање директоријума у PATH) за Емулатор Терминала за Андроид. + Премошћавање наредби у Емулатору Терминала + Дозвољава апликацији да премости постојеће наредбе својим (префиксује директоријуме у PATH) у Емулатору Терминала за Андроид. + + Препис са Емулатора Терминала за Андроид + Пошаљи препис користећи: + Не могу да изаберем активност е-поште за слање преписа. + + Алт тастер шаље ESC + Алт тастер шаље ESC. + Алт тастер не шаље ESC. + + Шаљи догађаје миша + Слање додира и клизања терминалу као секвенце излаза. + + Користи пречице тастатуре + Ctrl-Tab: пребаци прозор, Ctrl-Shift-N: нови прозор, Ctrl-Shift-V: налепи. + Пречице тастатуре су онемогућене. + + Помоћ + http://jackpal.github.com/Android-Terminal-Emulator/help/index.html + Терминал овде + + + "Терминалска пречица" + + ИЗБОР ФАЈЛА + Спољашње складиште није доступно + Или унесите путању овде. + Промени тему + + наредба + --пример=\""a\" + Нађи наредбу + ИЗБОР МЕТЕ ПРЕЧИЦЕ + Окно наредбе захтева пуну путању, без аргумената. За остале наредбе користите окно аргумената (нпр: cd /sdcard). + Аргументи\: + Етикета пречице\: + Текстуална икона + ТЕКСТУАЛНА ИКОНА + + Пречица терминала + + α + Р + Г + Б + ЗАКЉУЧАЈ + Унесите текст иконе + + From 59c599063ec8815a09b79cd65eb3a20fab25732c Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Thu, 25 Sep 2014 04:55:48 -0700 Subject: [PATCH 733/847] Remove gen directory before building. Should always have been done, just didn't notice until now. --- tools/build-debug | 2 +- tools/build-release | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/build-debug b/tools/build-debug index 78d77fd2f..a8965569e 100755 --- a/tools/build-debug +++ b/tools/build-debug @@ -32,6 +32,6 @@ if [ ! -f "local.properties" ]; then exit 1 fi -rm -rf `find . -name bin -o -name obj -prune` +rm -rf `find . -name bin -o -name gen -o -name obj -prune` cd jni $ANDROID_NDK_ROOT/ndk-build && cd .. && ant debug diff --git a/tools/build-release b/tools/build-release index 7a989aa44..586ee466f 100755 --- a/tools/build-release +++ b/tools/build-release @@ -42,6 +42,6 @@ if [ ! -f "local.properties" ]; then exit 1 fi -rm -rf `find . -name bin -o -name obj -prune` +rm -rf `find . -name bin -o -name gen -o -name obj -prune` cd jni $ANDROID_NDK_ROOT/ndk-build && cd .. && ant release && mv bin/Term-release.apk bin/Term.apk From ff23e43812e1069b36fd4e8dd1a6d0dbdf07719d Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Thu, 25 Sep 2014 05:49:49 -0700 Subject: [PATCH 734/847] Add "Seashore" graphic file for "Feature Graphic" image. --- artwork/Feature Graphic.xcf | Bin 0 -> 173306 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 artwork/Feature Graphic.xcf diff --git a/artwork/Feature Graphic.xcf b/artwork/Feature Graphic.xcf new file mode 100644 index 0000000000000000000000000000000000000000..b9675ab8236fa1206d1c50353f377ab54a666d75 GIT binary patch literal 173306 zcmeFa33wdUl`pPZd)49<+j!|sQm>L)t?sVsR!g$v4Fe&W5J)ByvJ=RFF-wAM112D` zCoCZlFiY6YV)lKt+3`jKNd`igB$Ht#|2LTz!eS(KSJnS_?yahB84_Ufy>H%p55DjA zJ@j=iajJ;af)@y*4!Z^}|DlA9>U_o0^(rB;g(W(0?ALi@Xh0nK61@hkIH=W=+tk#w5$U3T zX==Lum8PcK(e_>c(bRN*TT|2He`;!a4(WF%G&N0pu}KhLZ4$h9Gzra9O~R~~n}mf( zrGIM@zA(EdTu-@M!oQ`zf8%@Hc7dHu4e!5Ax_JAhgmbaUPduKHX zj})7P$2KFq(j2?e;cqS_dK^_*RI{W(=Ylg zW9o(HdSCQf#-5jVd{`syxO`Q55zzO=otTd~ZycNQsoeP?kOvR8TbE0$Tm z)__^RY5bkLb7K25PuzON#os@3=J@0pXPtfSc|W*-gl6xo?s($nOU~M78aiud?b*#2 zojHFRICH15?V>aHZN&b$ZKtv0;R_c{$99+xU9fl>)Vjmmdf}33P|FVUnTtZxpynOM z_RVMRjhT;M6rLs)++n)&>jxjOa#$Ml4cO?WS>x4>haa?j$UEqDu#0Do8^<3w6sRNTwT&C! zJ94;X(BsIQ**ac3`OCvYF7!exM7}&C0#UgT5c9}ksR3viH%}N=04TOZ^SHV3Q|eIb zpj-#OwbTO%2MCUvCw2cHM9cBOx)*#!dQ#@IK8{SXmtk-&IuScvhQZK702OIbv z)HuED$K=-Kz2HwT`-l9T|17oV1AltiKjdrR`@x@H_TzD~`k`;G8}_3^o<<-54bLEZ zC4c|YFJ84@$!E9k+_`I4dN*Fl$>*Q#-A%9LOWSk1=#_kVXCHEN*UtPddL>^d&`bE% z4)!j-zN5GkrB`_ND$DRn0<->^{r-m2b9YR<@bu%iZ2IAOXUrU%96$4{bI$$#`6M)Z zM|Jz-n}2lXKGV=yJ8I9|bm1BEr-3tf7+WtqW8Z058{VRaE?Crvz23IneDM6m)1cPv z<`WkznFh6NH=n*RG!1IrZoF{Q8GB>qV;6>}i3MLUp4)uJ-kAC1k0N`CnU9`1cdUBa zDI3<0swYm4u3LZ7$)}u(LBAe+!Mb_Q*u;i4U;Vq)%ZDn#ejB`e)>!qFZyva8K(1py zoH=G3xBsBG5+JExL-pLYG2_%DhWrllyfxlBW}Nh;pq9<>D7HudwH_?OoCF%74F?butjKk#^(!Tqm}$@`tQAQ~Tf9Wq9ThPasJidmx~FzN-veieKMO)`wSfl06tiD_a)(-7Z)zX6e% z%-)D^z}Fz7Fd2rt8hjd4HM*2TUJ}|HV(;v4cE0%XFVnBWjlTQXwkMx_>Z$b8^v*u< zXz$Z_XW!YrHTM+W*~uMG_8~W)elq_Q-q{zPC_II?^v!L>Cz1VfE6ZNurI%S|{X*M@ zCjW{3zQTX!Zku@G;d_2^!I`J7p9zn$;UqYe(@ALdw(1l2Ty@TdeWszaw$>iLVtoDl zY2eJQ#(m@K_nn5dZ8e_QGQMap%-k}zxDong>l5a^V@sw%El-#ajfbW|%}*GQZ(6@M zX5Kv>o+cK2+<5eg^?PIH17}C}5;O1GFn6?i;_=6>U85d7x#pO)#~uHj?_$uu3_fmM zHD`2U?cx7$(5mH2`Enre{8^*b6As&d1a6Bh5yH-#IcglaA7AvA^^4$R*2cC`<2zp+ z^3!X;k@c;k#<8Ct3i3q*nby(jcRs&#a4^W12t&-T4oQOn7ibwZzr9p$0Gdb56IQDL z*n;_a@KFF?9#T<T*&FfkEt9RK zFd4RqVu-nBm_uw2^-}Ob8jK6!oo%Z64m@|$HR)>uO;f(_y!K|*%vioA!&~`%ujOqr z=6v_9oaJdU{3pNJhur+!yLn4(GH1V8u#_fK*i@u!;TtUb_PZ>**kIY!CS}&O+9$u$ zW?-Xmsx|oAjZLS%xKS9|jnb}PUHHQvUGn40uDJ5*Yp%WihMR7_^|ssZxa;nF?|<;2 zM;?28>yuAE^X&86ckFsm{F(Yw1CI1x)Li3{^!Qcj+Kz^_1xpTTe>R%12A6?YF2-s9B?VZ0-C_E1-c(p zka7K*2rO8d2TTM;luwgvuD5}bncRHcqsdHezOHJDjj5VyQwk_vv!Z!yN@f(*ENfm{ zP6EYSlQf?#Co+n9DrkP2D`)|mYeB<0Ogh>6s*m;DLamdPYse_v=4Xhf->h6i=3tAT zjl;s((nju}G47*d_h%#Ko$o{LqW;X)MI0oVmSNt_nsy<{GVbX|{{xVcaFB!)pnC}l zmgeZp3cQM~!qXdtrl*je+}N~r+pl2Y>H7p89Ci)#*kIdly7`vdw%l>|J=iro{K#WZ zVAmi%t2`q=Ek7kcDSV>x$&7u>1;aiEZ9MgR*iM|uzEGck!9_o$?F8RVTn9UWB`4h` z-|D+XzFF8z?-adf7C!p~u$`F4DE>YQHb!qtkS*d55}Pc|mzzc}{_SP@YzvGM?P^3C`x>?z`2y{CBE%D7PzHl-qo_dTz09 zE?`EBC6BI^R5$4*MK6_PmdYhbr_^5(S*nx-eV}Bqv{BqzvdSnS31x7Q#BzVheJ=Lq z?zEli4tcve?WrhF8ryb$f%(^NO< zno=xjGD~GmDpKm#M3yR=P#n-KmNtq*8lI4w1ihl+>1lY9EPasMeR`X`RenNwTz*V` zRDMKxSbj);PO4N z`Kxnqm2#!;3VD-!xqO-b$MU7hCGwBNi}8$HFF+oFJOL%g6Tru7PEvGT!ybSok6zTg zdO^c8`U`ulQ+Fxw59DS6z&kV{P8xdqnkJ1 zbmNUTTz}nlSV&i2{lF(;|Cdh4n|nVj@73y6>Xq^pflboo!exKtYwP0r+WP)^=jMum zf|L4+{sN^!(aTb?=qWe}KBJ%)6_(&n3h*r~^%ungCHNec;D-uWUVp)>2sg{vr^(nW z$ZVe`UL*WOxJtNE*d$#3)@47w^wLXygvEc+MHk-ui6FMI|D&hp;){JhlrK^)R4(wI zFa1FH{+{#BJ?EUW&pzv{GtUsmg|X`Cr+x2xr{=Uk(Mf%pzetIWT+XqJj$Y33i;i8+ z(d&bbUCz-9%kQr`%->#p)m2wsv1t>m?WLF6>y0e!dFUYP-mLM7v(CTdsym;2c89d{ zL#5}9*Uq{8_NT-Tms~LZ+F3uk{rR0A*8alr*U!B2nGcg%JpP-@KTKxH_=k4?f1Jqw zI`3S*kj^~gjPbFt(@#6?d#7&Pc#3%P9fY$wF$j{>57BC zvS!1K@uXJO2UmY%W9xWEGfFFuYZ>p>jPl{%3y$}o{Kb<3<5`ry_&xu4uV$7G_mAf^ zvwWOyyiYS%ZuC-C`i6HrubHbicqkiOLz$s}<#cttpqU44WZ8<7ShjRM%lb!Irk|*e z7d7ice=ThFNJ`N2EEn~@X}P55Sb6V!?+-nv?i8;4mn$}1&Q`d+oxpAaOB_>o_E~41 zdB*sxv57N(@S`j5*!IkJX~&03&l{_qec6^L#SfQUF!tJ+7jJoP$CY@){#yGB$6i0< zil;wJYVp`_F8eTZs>6@H@UyVVodtv~s+Sz{fA z$p<1l0`r0Y2W0``XEnnL*)?G04Z*B~Z^`QKP4S}&N%3s*%AL}ic z+Bf}Uxq_*$^^Nrv%;l$eDbv2@9m^NYRqH*JRgR*}C?0m2I#wu{2cFEbW$RfsxQ=C| z6V$(zUcb8*F3uQ!`fds`uh3{ANnw< z#iPIZ!H3B#8U4`i|Bn-SgUmKx(`2?!{O)(Z^PLk;IR3cfjy-m*c#LqgdQ|o3HEWMM zan@)@ak4ZpvikGiSUY1hS*&W66<<8Qbu?2nv}H%MjCL0d{jd{)qdmwEUK<$A7LC%u zC-_Hui>7{8{e2;XR5(Oh=Cmon{(-qC!~T(Q+p)8QSMhR7VR%bMuz)VORCkj+uZrK zVzuoucfQWES9$g;#hS6p`ZfFgjr+UPBKm#5*0#2t)<5GfWq-7-bq5i~_@z&^Fse3w z#?{X^Gg25g&%KwE;<$Omvq6S}JnMc=O5@g5xO{<*NaN<&&jlDMk6ZV;)b%d4$)$ei zQr~x}a|16>HPHR9P|wr2neN|0J!idU*YdYe&$_jIENY{@E?)h`OTWjuz^V{-8rz?x zb+Oxc_W4Pyi(SU{muOw=GIqX9>jFyvw-#D=nJ>_~*k$f`3oBu#xr5dPvR8P9<$>o< z>w-OTdo8dhIpcl5*48$LnHO(uWe?LByX46hM%Bi~uX?VTk;0gH&OMwI$IMO7@Ff89 z%=_@i`?MOytScSLJnLEe*^XKFxYTtnb-7Dj1d5*Hm~~!&KgTiaoc{vFpC0M{Efjxx zRLjSwp0V&YVjSlLK0fudg(t>z8O7GctJPn={Nm2vU|l>dJSE_tvmSdF>*8tS(I;qK zJY{U%{tng!{uy@1B-X`K=95ojT|8wz@dDPxljgQJu@as%xBil4+g{??%UBWHtY5I- ze`3E_FhKY(AzLl?*SfEbmCUp6Yh{Endd>qaPgtn3HoESIk2W(>7&TAX%t>+7JpbV! zLqTr18GkYkS!vX|(4owe9|^&n|rT{J1zRU!!czsPl%XsU%bG%K>mhtAveos@2Wxo44Pg5%{Nz7K8+AMREpiuV4LYBSzZI&4q zv&_6&X_{eK*M9OleFpHS^{)kS?aWv=%k{Kcn zVs|nf3=A1xxn+`JPqTY@!*s9BSo4bsm0z5}-M10a_6`{bzh!!+gX&Oi@GE~XymeH8 zzBp7J&_^Dr`E1x&H!`B23Sp?KcW2i9$#3Hr3~=Al$)Ubfa_~`OI__O+4rIlo`T0Kw zNd776odkJx=|rU`l}aX0u1;qtOd%yC%}2~SM}~^D)EvsoDKq(c(5=eG`!j_9XB}4{ zFD^B6X(eS%)?pQsAS^WoGK5bw;>xhOG%u#*l=UBgUkN@*9rZxmT$*9!&XU2iz)L28rC#iSJOhuJot|eZme4d1##HaGYU9{esdL5XSXbZ zC2$rh`^N9~<`{a0Yh|ySzyo?ExQ$ILw5r3Eu1rQoAB^rDQ+TE>&pT2Z0*ks=>)ZOS ziKSulWa!m~dovl8)P^tnkLfIPxL<^tlrr+)eglil)>4>Omb8>qBkUcqN~FNhe+q7; zo0rQ;+8pX7TqbD;SE&{q{Zi3@gB$CXbA_c$VLF3pl$nO;YG4&|jp06OqLCqS#$XpQ zga!>_psz(2|7GwA_V(BcSz=hE;4;)@DrQ(?jn{F7D-1D(Mwq&4=~PV}Rk`pmgk={t z*R8Pe3c4vx$5}0T7k->f$_hvRNH9b$?=sY7%E)`m-?{mVJ(*0}9HFjJF=g%Z@fCQb(x zCOHi>*RNDiwYp3gF;-ORb!QbYSOq*evh3E~f3BHW#STMdwFo1V%fEQ)4gWk*#Y$x? zWBp3hMRO>RR97GR?aQzovAeTgv^jVd#YR+R!vZ>7M=Olp+OKVx8sV92Oya-cU6A7BokfujtGgOmN;iFo{w zH*M30p-D_f7@R0~CBQn#v6ePV88Z93rGyb@KESR`hK@934))0jGfqBWI$laE35#3+ znJ3hvKwKO$vdN^JU_QXcRZNI5RO^TD0fU@?%Yg40%1KEnVLJg(Q!WcpneX?2s9oN!>Q1XN~eq|k^;RL=lw zLTV5u*jTEO)yEvNN*-scp2P>4_;eUe>P%W_kXBeHdwKtzO3YEbm#C$}#4T5x;pifE z5puE)8>7o?c{U={LM8>zQiL&~P8Lv)oNUBd^#c%Zge8ch_M(i%QWutZ-9r#=gk=tw zQMK40ZNp1*GPXddgv<>sW-fh1PHKS7(d8uW8?G*UXY&`j#FXV~fJG|a;rEuAwO6h? zB$FaH#ws&L$+ya!2UGu>+dsV*PZ{wHo6ESVf5B11Y%n#(!b|h>;3I5`SUW1o*Fkcu zv;&(4%IGZZzl>$f(%bqn6P1UjMsBe!J!h=r9!D9TT=s>H*T2pTJ>$Sravjfbb=4t9 z{+R5%!yp}cGFILJ$x$k7mVhpuj&fNCBk#!D^piWFJGW!ls@jeD2xGTdjWuKl>B26H z0Kza_x@DO6eHh*1WkEn}ISIx@1;)aD*qF>Lw3qIJ?RPAi`C0G*x{pidj?}9IgI{KQ zdly&HymzoRz_<4{Oy-VYdw=huB&57bwW1BP9sbXP_fwl5hEEM@U1_$# zw_z`OC=X2z=8_3+-(8$c>|~`UnMfQ;*4@T^kQas~`n!{0Fx&1jU}7shQqo8;%WlJd zdvBjK6J*za8oZA>!_aa@NiwT$g8?#^y<{$#O=qaN2G3krTC2eH0Vk90;AE!y=*43e zouS4WX4r=C;sGX${?p*S)Siep$sEA$6vq{|t$(orhj*bf$?;^-ZCt`DWNIA$ELrph z+|bgfNJttQv*%fCj5TEHlwqqZvw<3YN0M20dD!9% zTRo)54BPJD-E3yKj!rs_I2k;=<%6stX54HAvUyI)&xWDGCoJ@TKN-8dTJDVKa zl-lTyAVX#JTg4n0%b5poQ9jTqcdu_V=x9LeWper7Z@htf0LC$oSt+ynsiW2X4*53o z0S=3__!F=Scq$Awj-kVW<7${6*b&@DJ6Z5UyygQ1e!EOxJ-1RD5n zodNHlMK_x7_prOve!9`T-xuzYtm^Y$J7hn2Qo4)nFXQB~?D);k?|%7@lC|gUSAVwq z`Fno2W%n+{(ZfR3+hJif=6ZA}?de|q?&`lm%;I>ea*a-pUZW623(mcz5L?M_%;m zRLyc_YF2e;N=z6@jmvlkrvBmaKZ8y8#O1PK{`&85JW5^8E|K~Ms)Jv-_rZhdwmf(f z7j=V|yi~+!wjk0+ZSd2}*u8P7Z2s=cN}a{;xQk_Tr*5M)DA?EC0l$Ew7OTgFTl*09qAd=5dwMUNo=&yAtCGY~%a*@Yf;{21H@@Y$=4ZPsLU;(y(WC{)yYU;iv?sy6Y`HHU%SjG{F)+p3z?*Ea1(WpBUny?>dQW7Vdf z*&hL32$)C>p8n`dZ`Z0btXlQoPgqKk3_SgNaAwVXX7qwvUwWs_s+qq%9uag}a79?a zPNvqSE!!*to!Ddg@qk}DjA|mDk~C94#(<6ckH7t2-onQeqxuM*J~8QV$(ZlaP;-Yr z^gDbTHmANrs!Bg?kQmoAu9p(Et|MQlsaUSBt)f=tX>2)Rqz?Ewbl*OXLM=%BY!^k% zLRU?gpMD2%myFx?;^Fxw!MqdIn$N8wCL&qU-iQ=!=I8K*!!UP!j^Z{naBEOaVio4z zgYGX6qgW0?zXhjmBzdbhh_vW>aGo`Pdj!q0&@aG&PiJ@k0qkFYgW@|F{j|xlSdVX@ z!@k+`E|@?2dwPHZDmK%c$4>nFvtDigG%;yEH;sA06sBq0Fk6LmFm$0eO(O_VzSy*`tc-8 zPR;y77HhK=k(BmK{C{_y`j3aMDD`a;tqJ4y6`wuw`@csUQ|5mjpXlnRn*0^P5^7WL z{`%>=xBLQ2-u&&;|MYg%Yfb(6%0ePpkkL;H+Ep_R13LnNme%C$MZAVw-B*EW{L}rg zD;8v15O`@HEaO);(4$GS2E3IyR(0ynKlyydld!TR2 z_mQ7l+T|gElty*n#%l>5P{p*-a|QUuovD$$rxJZgm(yjl9xz}Fhw&W+z3lyQr8rfxjux}6OpA|z+(w%a*Gguo1) zT+Qxjv;@3OXKv>=KjUuam;d@F+wJT=L`EfTI(Iwge(R8%$?e?#rK3*y(JedQWNwE< zf~8iS+|DciQ0yu5JH=b1{&$xj{H+ULA-A*p5Ct+VI=P*M*lENIJUoH7f9eZxJG&22 z(MpRBqwpEI(@b2(V;A(-zI^wCf2TsTSvMvw%zzcYgnK0N6aAkW_CT#!H-GmfrGZ8F ziG#YiqgV%>`_1O_i8@Hvk%OgxZay7v1WDbmn=d5mAiwGD*NvV1akmq(cFLPm^UaMZ)7ngXy4CA^k5~qvKRr8O>#N#4o)Qs!mMSg?YLo@i^Kc?fIoixg- zab0J|uFOO8KY7nLmnIxeBF~jnojIDVSA6e5ax~Xb z)o=o&CF&E2%4x30`M2W+6{aleLSiDGI_SIS-}*D|bB-P0Au|MUqwJmp z3A&h&<7NVmchb1;xGlEdc^GbkdVsU8!+wv(p8e*7-1D43>cNtPs+qEBTqkwb(%<5q z=T|Fv?Q%kN*>b0mSWTYitz#)}hriUwR6EUs-iA#zZsj~BAvlWHdpdE-2k*HRGzI>W z%fniTL-DiB#P2+qG<_u;Bk?(!#$&Uy}#MbFl%5)2i%Zj^}Wia4*v zcif&=)7quvf2!smx@pM<5jttl#2@b5`0YcMYrWWzP8eI3eddUB|AjlC<2uu2db6Yw zb3pIF0p0dT_5!O-`)Q8 ztQ7ZkTA3E;S>0l4vUl}A96j~|IiOufe0DIcf@>rStSR=58XsM{HjxXtLri#y*+u>E zm+`4YrVlxcce|MIq2XG*K*1FJWn-8=Z`YEU%bD|$xMr#!n!d7(=zuKr0(_4hdcmX1 z2nJ}Z`aJVKU;W&w0UoD8MMQ_mk>fVk{m<^_?!M^gl{|2bs!u=o+<5p9y^{gN8))!B zCoSV1PN{n#2$6NM{SZpc2t&Y26l@1CE%!yg{UBe2iEDMSgPq07k1g^2}czlIq}LtBlIo@ONRct!xYC#fXD5%xkIaiJZwjxM83wA46w%n z@R>v-Na}t_rs`mjEfjyr*i~_?plRIF41Kag#v9*qP&)r`VPC0+^N(W{VS>6zoNNmn ze-e&8gn>zDq(O#-d8G{gb&aeRjz94(yD}IR=9Pw%4kuaQO0XyF_+xt|m@-F4#Vc6% zN{M))fcT!=jM!f3g3m7>!d{VM_W5VS7ZI$J5V_=dC2apFa*AAXz0xW|f(WAKo`P0m zqeog|X(Bn$;FXRaQkXL0ttBTC=}({NdL{TPrXhfXqe`YY{Ven)u7@~m&PkNKl5-k* z7+XK;0sd;iVdFd$kN4KS65~&|wYAI<&UuK}KHZ!e$^H3xh$6-K%Z*GmZl8y4qIN}2+H+2!GxC!lyRv;MmdCQV-%f1H3b1*zKXUgnh!^FY^E7I3=bTv`{j zD!Z9i`W8*N7i=|YdnLx~;)fxMSmP=4lcDXEXgzjwuk;0ao(`KGh^}X@d^Pt<2hjph z%hOMe@a5bqu^`R(QbJ$Y?X#goOSi%gMHB-^nZ}h9wRo0$r9)_i%6oAguXNKO=k7VG zlEXiBCG9gHx>n8T>{JA=)I&=a2mG?_l~(9E?v56_8&^7aDkQKrmZ46)1(Mr38Uv{2?JBRmEp)z4&vGWSY{rt#FX zIHCsnYh}kPZCcd}2Vf*L*4CcNG31r%CnNGoy%HRV)ztz$9E;L87X8D~{8+T>h=YeR zD!4|n$eLmw*6~M1{9tp3h+_?y-P8|%KmII7PS;Z1wpU8}&~PnL9KbQt-){`lH~CtM z`>ZVz*GyNS>FeLv)?`U##G^ry%ZO2fq?GlNT1NaDBu^Q!Y>>QV#I-^4mGdn5%YCIl zIadmnd)?Gr&Xy>(lzU1RLLPS8zXIs4l7MiJnUV-PQNsS^KHKqpK5R|TCY zsnCj-ya@70zb)lX{I-@mNcB}x36YxQ?0w`X94;RQN`Eh}aQUDjnf_g7Y&3aaA z(R)~G)w?xHZF(0=GxUr`X{MgmX6Y%GX6s3f(i}a((p){R?W1?HG*9o)D9zW~H49O> zV)m~9I;x3?&PBiT^@t{crr-H`Sd;M^vVRrOOEmtgf?lk7Ah}rc;&+kzJ6~U@`M}*5 zzYFvQnjiE6EdZGX8b#-uuUXj1%-823r!vpy>-%T|EAxE5K3Cf)ZGB%{60wBXFG!8C zN%oFQ;>0GQ_$0nko+W>&PY;xEIUgxl>Sc-A&+7b_+V0WmP*11IedKP+1 zX*POFX%6~IX)gLnX&>~F(meEUKKevy0s6Er`s1X9=o6*Ir4UO?N=tM~q0(ZO!lgwz zrATR^9xd(5QVc7BQaheIr4BszPDDy{QXH`k69{gY#D^J5DSU*Xl*T6(N)-FVhbuai z5LSUDgjy(43TnM9HEY>oi`G+Y)w{n@Fo`VR=O#!%`M`ME#mEE32$?eQd*nI(qFO@A7m>WJF&N|_%J)Mx2+&M zK?zSz!&7wALf8gMc-k!C>1&j*8dwU$7Er<}(XeV*is9X-gcZe72i|(DG&f=OX;_IG zRwYYVr5dHQj;Dr1@4!aiAdmLcThYy85-w ztS9D+_UdLK?x(CL=8tBn6hLNm1*1KvHb$A1Y>uMGMJtae3*D6>8o*C?xW8!RV524fMxi~7-%L1M)@psxaJZ!535b;S#>B8rC5f`$2xV4W4r{1F2^Tdp zAEV4lt6^Nc&b6yyT*oe^J(TsuD6^8@Fs@GLd|_PTE_Sf2E8+{|W_P~BPgz&QAI?%K zfXqq_aPc>t^<5w)f7kCCprGE|uF@8Q(h#ozZ(OG{DLPqqAKB8Sep>K7bV0z(>t^OLP``#mf?2yjCjbd0E7} zl4y(0h*<5sn*e%ZGooz~e8pjH3iN8tjJ8J1Sf7JfJ+q@N5i?~^hM^ZbW=ET83i*uT z+s@o*Fk&X*1ZcLf%H~D`77DdH~m6*e-S{yYZu{@`jM6E8qbQVXkIyku~YR0a=zJPcNMjqy3v;4o zM_-%HAIThaWu|UF@?tN$6Rs4qELV#?({fL-o0Xm1TkK+GC-)V7g<4;4wo6Rcy0X1} z^BC8M*f!PPbZ0aqFR6y2otfNRhWioQrj}1eL;fYTP&AdF&CmcM+`y8yEiv13vus2t znB9??OU!7`OdBx@naJ!VW~66^4VXRQxm0drWq;A=i-q<<9eu3~@D;0tR5%oxyTk~k z^DQ<8gC~+yH0L0&eE@LaV-jANVui}>yzGG=h!J3h`kI-9rwA{DTCHwgR$ zq2LlMLA#s{&kY4I^j<#4R_8oCIV;JZfEAe^@?s|YcyR#=Ru_L-`-apdSa423K_|NS zlU;}v0S+%L3Q0>~9oQT#3W-asjya5895R-KbDUlhvQm8UEDm9PaB@+|3}^PS^^Equ zq61r9Vv*}Dgml3v>xW343S;YpSEkuvVMzD~&qcbt+>`WUmid|SY7{&}6 zFtZVik%mPb@@q!EJ-iR<$hR`Ur&WuoNEmC$2xl-6B;>=^BPx;<9EFY0E(DX<&C6a5 zZz7dDc-e#PO*;W*IN!`PJerm9qE@&oR#ve=>0m&L1Bzx8GuW75n&S>ayQ*mbZBJ&q z0y65rMg>5EYseZp5S|sXx_DW_HmQ}$IbOyVBH!7DSI>&_egf!<%m}w(-Esh1ihRe+ za4VKX&OxlM+2Ix}i#!JmEgzd5ZpIw;@F_F3H`rM%e+$L0ywhxa ze2QO#C*A*Z6u%@-p$qm3L{JY&uouPG25EeJimwgQ{jXDJ2l$E@vpzb-*8yq#Efik| zRLjSw_|qrdk5BQZPr4tUa-Q$mb~ps8<)dOe90FXXnH|GhJS3mW#FY=vFhuc;Rt-;Rw}x(d!x>k^}4JC zSzd$HP=mC}=~yEw+x22tZdntjxMV^;!)z$Zl)(ZmpI+2y`TIZ-?$_ zVx-3U)vI}}D&6wow>L2-qK&eF5V-D$Lrn;Q<~4Bm6gRrrexW5a<= z#p$WjA&xs;#c=J8qGh`YS^{zdE*&VieXZm8pLRDYAmD>L;!jBpEek8HA;x+T3v9!u zpd=k2*&{SolyiIY_}t2?Y9m^2ufs&=8d?)pO|2L#XThn zbHk^_g9KaG7%AlCy_vkv;{L{dLj@6R$<;MBd>YD}Ke7L+0mS*tf1m(88)H9;7%ri4 z(X9YoV--jTnyX48*i^qjY`VIP8TpL(X#oug2TmTaqKyBAg*($wHAI&` z9Z}pi2go$I$H*1tp>Gs|XiVM`g(J&N7r?!CfRZ$lR&jHFAc$ zbB(C%w#M8klQq}v$AxJw_JUQrJ;80ySht@mt}J6Nm0l`Lk!a&IA$7dAZuNfq)thDA zhPb(IHOb3xk8A?@tI1(U8Vk#YjdiP;IG+^eOPj=m z%PJv9Sg)QrS%#CttK-a{+KnC{?(g)|cC>XyU4yw!yM+Uo3QYw~(cYZ84s)wc>(Hrl zoUy^(9w6+`$H7*(J2*Ilx&oeCogp=}F0QaBL>q-N7&F$B?~*VWK3TOD%S&X<-^Zkt zQyVU3vknt;)6kr_(pWx_7hSHoo_P_cHxnyIba;`vHuz-f`7oqa=5Su>6&kgP%e8B( zRt)BpUi_Vc+b!zOI$(_swf;V9(kOg<`c zwXte2FTzpO*~WVEP@F%pdSwNUq7V9#tM3TV<1tp1;Rz&EAi8|?j8z~VV6N23QBeJi zO>rKuW|~l9^TqcB~h(tPC`c;fD|Iwb0@1!Q0r?blW7zKQ%D7V5Ci>oe6+e$K6%KK36km|DLF>DXYJrXmjtP{pDWK;R5;cD#;6Uuk2 zwn^O{yp;`(Z{W}%v#1SdgFUrTzUOtVsg25RYs{81)LiRskY#?9sl%#0ame;D;iCb&A!Q$1hV~JgjZRcoq zm6dxj>NT+w2-7gul1(P-#@l{B$7!7%PMp?lgUQ=>dW**jb3(1ykI@~RYa>YkFC#HF z)2NwYc3KztnS!y5wcPFs_ULOX`JrMOlPlH_6^x0wX=qMdW#CZ3?4I4EYgTDs6RU8j z$ni6W!{>(zK3O+JnS9F*u> zVV^p9)yKN$3XzT-^^?VO!J9Bu#C8V8PZsv6gt3id|MJOVn!pE~EEpf1+xui;A3W^l z|MJP=zctjwC!t1+5k&vV)YANd!WN0$%0{#t+}V~Y!iZIoG84o^;Emhll#i}#r^!d1Hq!EN_f+}O)LQK@lRRu8L z8A7~N&fJXX0PG%lN~bj(0w$tULa_@MPin;C)gojZSjtGX z1Wr|)GH#?;1Z*Ju5Y*1Lae!3bk4#A( z3=SZ$WkEvdQM;xb#DfDC4v`4PC$gZetkY=-2L|8(5yPj5>+g;NUDhWY@`J8ChJi8q z0i$+JVzsb-(j9sKfJoAaCP)EKc~>ZefNwwoZuN6<)@WBhMWmz?t<}e+0~84&nRj5b z(%n!?2I3wjZZ2&Y6aEr$P#!SLnnLX&u2{X@R9PMr2Byk|Y$k=>#9nhjfk3L20%C)R zaX2*69_k`(nk!FYGFj{|b|(``9Nv-osMouWSQ{>G4);1uw&HG{z+!}&{g^}wiR(|c z0ECyO>Jf#^o~%~1NdfWzBF3fN;}!QhO?t3snp`mEwSS$B*b*VNS08F1JjFEvpiJ{b2e}V$d3d?FE1H~T274|1+ zpER{>sF>~UR=cQb8U(@ea(ouc$~H>V>PygP)auAUuBV6N)IA@=Ys&@-S+PfHs03?v zrL+Q}y?W&ywOc{ud^Cthf&GI~$z@d=r&UYq5UzN*C}kA}JL6Fk#`qZ2~Rk zdKFZmplXt(ek%$1;`2(la(8X$EzCD0I zKdu?NC#qs!pEw=|pj`o$Mr^ur?%HIH|vm+F+t>aa1G?h>`YDo5DtF?mf5)I@dZ1T4gm*pst=MhSO;X1m4+H+NUD%TbUM4?K&RY+(<*DHo3}&S zQU`@sbbC(i1OU>(AMFfg>pc<~2ua;-Cx8i&QKuKnpxos=u0W^IVbR$dAFZoH#U9$4 zIDD`lLNI+@EJbxI1*dK+gY}@X7 zKh;irQEL#UiSxucO17W3$){84#J9WmH7IvVZoPbB$eu_*NmhpUsJA9{%8*roQoYUt z6oK-38(R}Q$%l|9bcRRET-VtwOj(v+YgJ6 z$d~r+{Z@tZvIF3Gz%!TPPx)=dTWn@Z!!>HP9~QqTrYHEu{$?yRR%@0nEfe$Y!E8Fs znVS(98C(oANjI(4epq}h!+>!XuQx|X51XrEP;zapM#lhvDeJP`pLZ6w9cZQ;WHcT1z#LY zsh36~_KWno!kn}9!3L3{i4*%Jv_-K@DFCW7cxp%Jm%+h)zCi^y%$qClx~R+2_zTX4 z%&vHI9LJvbWT{Iv+?EG}%p*DrR|O6qT`dh2 z#2S#vw}kbw|LxI`CzT4RxDpi|1{GePqPX$wyZzbXu|2`@+AeZW7unpO`dvZ#B&UH#^S6=!s)!R zcMs_l(bz-&nEBa)*Wq|6PlEog-N)h`%q>x*)t2(y5`|H1DbI0pikaF{o?W86wLGgt z0Z?1)ytO>DWWh=iLKFoMZ7I(H)ruW#3vSI%=~@wni5Nb_nGy7Nv|^zz+nq@zk5JVi}@)R?0f=!${y^v-*>5$`ezMKlbP&4?i^bK?*sm^c$tZ zj;JZa((dUm>4n}*qNC&as3jxl%|w4mEA)1!uvIjltwpK|zD`Y+fMk+*|I`li=rh$b z_8nISCi{!oR3Z+mqjca1pKQguGdWP{FXg)u&7G|sE$x9=EuYQc;kIgu;>g)bjjIyBIaC7y5TqnHx7GkeuG=?U@i+GCGC{E+9t)&~f!>000Rs2M;2xjlG^ z`g&5G(dctgD_EW=74x~CbR26yh?>vT=2U}IzVf8j2Up$M?v0tzqx|E6vEb?0Rg@=7 z`R+t#M@JBFhrct>5p18tJ5%gU(}d%)ZxA=LYdPGI_MzB}YqtKd2%!LD`4}CH1t8`N zOD^sW%UB7d9*8(J>3V<%ZQK)9m^|Ssqj_^m*yHLF?X)F_y}$$LLl9Tcy)Mpat}BLp zZvA4|@76=K)26l1k=O@Bmy4B>&OKO!=kYV<`U zSJ%sEl5dWBA~Ks=>Lt;nOSl?QTwS7@?cOL6)y0W+a5>^}b)|^ct&?cppCIlh`#|&A zC)&{!B7V0{Arf$9EJpMG1nVeHip~fv9D{j}15uHME{W(83vCk7WtN9o-i&Y|Jg{U) z=K&;_=oAL78DT?s#J9yd4}H1F%_$mQGv1k65$^;=Vhkbzo`_q8?-u%ClwpB8+GBEr zf-oXh*>cb7x9JU8HoSBIPCzTP5*X*tZKKH)AxGtQv5WrP*PLr;)E@(}8*t!U?I-NB_Ye&KJr`1a` zL_5z}W@~}ugSe|sYk~LS{p*73idYLyJw!WCdiuHuy6cNm)Js|mj;6CNf(Vg;`*U`y z{;1%t7k^Y_H1)<8m0Vpfqp6q8F;7%>*Ba4$4K~NrsN(7p-E6n3L{%3j+QH?h$JLdh z^p%E>ifF!mgE2Ac1I_zSw4*CT{cfE?G~miujOP6e)=}(=oj1b5wt)9I5Tor<1~G!W z5b&2$H=_Dd>Op8;N(ijQ5`t~%7XCy=$m0L;(JgjK-9}F~EB01%`J#p(Jpp}D?A?ye zcruml&h`~>6;jk?ptv=hn-hDxy#t&~cQ)4talhXBp!v|PPyn332h{G~zM`zRnbyBY zV|${pSbKYCs;5tDv#dYFV%}(5)p~>Fk8yB?QM}DU0itln^+pIF-eDqznH}BBaF>Qby>av@5i%r&h(wkW~!X>AW$w=9ah3%E0pNZ}tw^RmX~GqMM>8I86lx_S$2KCwSwYm##O zQD{5%2yGo!JLMYo1(fuN5-vq#mdfz`l=>qgOO=Qa9*9_BOlQQ3f{K z%<7&K?L1f$6niio#lWfnvyVx6fj%sWCg;mZ2r=p^PFKw&O4GtsCJtFWk%B^2hq3fmqH> z!B}s!8GWGC66=Y!#=2Q*i*-dQ&4^`K>MkNsM{}r)0xGpo){UF*8I(F&ky%}Bp)?9n z$`Dy1ltKUk+@?=bP^Foa^+aZd5|D_`LWVo)p*UFWlv$ZMp-zOkh|CF1L_0Apc=?y0 zk_Dj&L^?>(!d^fjnihv9!%@ss5ALj+1w8N zGc0u@WKpC!+(qG^S}5y@w1hJ#b+#h2y4%8O6k?PiU_dy9m`8|Dkfab$GbzhPXND7y zNX$ZJWoL)u6xE3`OmMgp0TH5e!V_dN;k*zy%}Ol@Pej@iG~vYs6i{k$crp^}#Izw$ z4&H+(tY0j&A@y=^PmRmZkEHkI-qtZFm*K&d6t6K;)kv(y&p z3R9X9K}Z!OG7_^9TBqOy&6yp=I)ss>AU(4v;*V398AV6|GOxrzA<1kl6=p!ccb@KN z92Am>)aGM_Ht6&%a0^y?A%f?TN-JXa6zc`62itANXo(`8h*M~WdQV7K83%<#F{~?g z!Rn17@DL zR6fb*K-9nn24)<1bJXbQDIx|W=$2>=*0mU8g*FH!`=X4-6L0U1Fmg5;=tGn}lAnV* zyYRq>o(u8L>>@_b1D!^cKEe?aDuGo%03#s<}K8!{rs*c=TMlJ!|U7XG6 zFb#GVBO`cBIs7j!P)Q6ysb(;$J!)o(tu|@IQiT>qchVz;Pl0S4-R{F47Gwf#_hJ`H zG(x;Ya!zy_1VqWqE(Tf1ommuG$0;B(Ot@!mF~B$|bmpDH42m@}ujpqS6gms@5iH93 zt~!SJGU46@Zox_{EP5FOF;b%Wdco>ij6hPPfmkUWeNF*!QegUuD&wHgkq;LYyI}Q1 z5o3x}f(T*~vuQ+5iR9`9E7ehyn2;Y)Q*uthhkDa-Tzw*S6xwr%qF@)SOwy()&`JSb zssJGm4Fpb%kyfIMQ3$va&AMc=z)3`2iS~9g0VQN55!(k!h`z zlFL9ash=voG38Q^+FF3#s~E#FPPzm`_oxE+UrzPUjIg z26UK4i|8;QBX~{}nFwSIL3$7j22^_pfAofsFN`$8af-VGx|5zM+&suQ`kf6jGJ&?c z3w}n=MzokdCl1W)Fv9fUa6)lsW>F9ur!W&iY_fDLAr1<0c1EGX4CwdF(@+^N#1W9x z#|mu}_-27yu#yY^fA-!4zN+flAHV0^A=6Dl7z7m|_vU5QoWeX$0Te~3g4){F*p~MF?d$WkcJTfG&)={3qzIDaob&&#z0Nwr zO#<)tZ1uhWpZocov-jD1owfH~YY%Imd(L;&0c#9Y*?^&`aWBy40og#AH!7~815rQ)*+jT^5O|J+!YF43RH(dNC@2KvyCy736=X<$9^5=mNVoD z)!?&@y<|xZUa}6`b@^;#4D3QJ|9oN#GZ-j_AZ=LopM8uaPUU`HF_t~m;Ioanvj2Qy z3o;n=XP))>#as}fBnTP>zVm6)wpu^Km?rq2e_U*fQ^r3s27mQv$E=La);~VB$$HZ+ z?H?E0=#=q~i*2xeVz=Wz5&L|>e$JGRFAM2&c1FUW59W+3CR{-OTQXFJ4 z+Vn}CS6(sh{4wgUO}$2|4p#{WAuz5SN3OYGb88B1L-GN|@nsEQ8(mjUfLj-$;6j29 z2Wys&B8*9>0K9)`$Pc0fH{YCfwf^PKt0s-V;0ySx)a8J$0;Ls9G5!KY5maSpfCSMT ziW9T0{_>Zvnuzzjb;TnHL!X-JQk_;Oii}6r#8c?*%f2TT_>! zYt~o3co9DO^75ckghSWN%P+o&zBWS{L9|5Ls`6ieHk!uYq@!X?*iZt&jBJw&VZWCs;* zx*HCeR0mSo^nE`>-ay&@mf?*_%Tn1!a|5`uV00x`61(IAcSDsOv~|j2^XK~S)janG zZO(49++6eoy+_6HVy#^*>L@Ckg+k2=j*(2Gc`S01MJBnZGgy1u1mB6yy;gHj<$5LJ z=9>QRoh{9ha*;`IL5sk6Y)PikJRZ5inQztL&daGX%9UzSE__FO za)YW?Zl!g6DkJxr&3KoUZzfH#v?SALo}hB2VD5eXT&k9Ik+le$d zjpn;g&bY*PJFD7q@&V&7pnC{t&T|JOTymjW%EV$I9?Gy$JWhhgr5E|_0jjA> z(KYkx%Vf0|9@JQADgcTurn#FITUr&Qt#+rFXEZe=Em@AmK?7o*aWB~dY%6F^JC#ME z@~wN_*`3PgRw`>l<(v1aHBrN=Mz0>Tpp>!U&Gs4tq)?}J_mVN;u03p*CFfK9q6FtlKWV;i+7=6l~P|Vq$;5n5{DL9H&wvf7g zH_*9YF|e)UHN}*@@rx+(%_gjXn#bVaekQl8Y_XMGTS~H_Slmv-S8OqEesaFl&tsCP z(os8gzrXp)=&);txm!a4H2tzcMdB0oW|LZvG>>$tdq}p?G*;an^wf{+Eq{KpeB6B0 zPVsl!N4=}Jd~f$uwuxPPwe}xWxEHy^d@ z<$h(0*tNp~LZ5h12N4G3Ary-sx@}j}gbB$Npz$sFY#GS?G63BCPFmlI0o9NN;8-Q9 z0LK83F2E{QhRNGhgOkbxU>4Lzd5~&sy1X6TrGW9U%4t2Ci8`wM&SrZZcdJR`NEsoD z0HBBfL$j?EGBZdHG!BFVVIg`cs3F8c87hj!@n*GJi?D)fFi~$47M)B$)_}je2m$a5 zc|9irQU~?dARJ{2!O3R4>uwtqUKTFJF;F`hb(W{NVcKV9qBzRm_FKd6&PG3=UP3v=~ zukwv)mp5>)?XlD6{5!^~UhQ3;)N>Wf;{2)Z;+eG7SNYad9^Ky69slFg>aOz5scL=& z#7y#D|HsjF5Hx4Y^hzzXH~T8vm`dAq9{b*>A&NV0=X`f{qUFcl?yLJzJB(W1waCK+ z{NqXw6I6cp)P{>aJ9=rLWSdiaSsr!TliQWIt#g^(xp>JorZ%YCX|yKo_@#2auve?E z&E6UAUh59*--xay(b=n`ySm$}tI9T}((3CrpN%KAdiwHtHWBr^`4FAM-))~-UG3tr zywT)MGp{sI?fu8y_532 z-BYc;cJ-3xTdttoZcbHuM_!wxd#Q&2>TYAIckyHW+8cc*pC?+Lt=V`|t1s8?jy_jk zuH7BZs^hf|oZ3-#5MQyni;}(ga_M)-w%jW}wfK7S>3&|1xP5NEbqDWW{rXo+uU$K5 z_bST~br7jt*B!DscNy5rs$IOh`*ka*R#lbXd55#VrWTR_NWv~bHxcx#qFQwohm&`B zx9HT#s1s)VxZRZt>lb|3H+3<@>T#92h zg%Y|v3L>GaEx%&&H(*He;-9QpmxgdA^~@)BWZ;E_U)k|;G|b8umZc{tD!l}M(e$X9UJ|lPTn7RKMT|`pb$+^rKO6y1#}*ua6*8t!JtF2t&aG(;fb{ zY9oLoLJwc?yv?s)Z3G!kz17wjL8$anUuuVFnsW&8p4v?)U!p?br-64l{Ec|}d# z7AyJ=fL766b^D1?d|5UDil#uNNK5Ff_Y*yWMcC@leqI7t<<%GYOLLM4$qnPihN} zVhX8>S#t@yaG`P(52D~d`&j5$%4`ni`Kqe=D0+fI&;Pg|12|bZR{@8ysx zIRXV3<7-qWB`W%VlUPx^yT^ctox=cI14N3KyR#rpX5B<`3PaM=tLcgWACD`N5J+kX#3ESAB)k{W zYmDleiOdBgV=HKk1xv-AW~FP@<`*vI>HryVLLG)k7+;`SJ)W7GH4W^zM4#AQU&)vt z_(DxR+jE~CgB^M)n^jz7Mm|k_f?xOJ%VSP=w zsD_|CsI^v)C&+jJUDZ_$Pvy?k#E;S37Lc~x={+=>^8Nc*nIx!;AoArgtBeBo{**F( zw7U^=2Z1{U>!TUy#6@i(aIHuZ&35-Yxv;=fQKqLasMUY@&0G$vNiGYqU z5=|IzE0oS$1OSCVtpM!lp{EI!VXZh_=mpUR(2qq7Y9hcg42(kQ3jW?HS0K4Gq%$B3 z1JQ7OLd5|3^Z~3e_)fSOUXl3*YJiIu69OVjmCBK(31&>VSpAJ-i^7W%M*kxDQCSF^ zC{(N?-4&>y_ymhB^{-MgdQ7-PFXsQ7dg8}OPS1O^VtR>#%V$*xZjDxKz!?Djf5kX5 zJRP%G@I8^-maJKNp~7Er=?OIQh#hNJdIL3;r(M7NNxv|heD}DJIKdJ&2&g2l*61$1 zgiQi(Ekr?-=jSPaZ6S3Z&9)>CSL|GSt%TQ?-s+EUvnZ3BO?uSJPL$&VOoe1aU7orO zh~!^3Zs)P;3mV3a8$WLB*o(hVB^{k@2Ku9oh=4y?c_G0y0U@u9VfjKM#H`?&;0xW_ zaTBo^h5_AFg~^kUkI)o*Wo9CC%y>Y$D?Saixg4p`#a;BfD{CiUZ%+wQ5^7u#+&1SsLMn= zv9biZ>p(-L5y%&4sOtqsx+?())g;Pa_1`CVF0%~7&7x!*My6!1w@knS``J+pU+)2^-4j28k+!+Ft7{5K=fN3wD^Ht z1P>PojhF$lVBsmNgRL&^^3-vT*Iqbo0v;cXg8^+X8NeE_4G&q{d0OkN03ri{5c4z& zbI`UFq#en#A5+LHRqpKIPPf)5u{M%<2!t0=$lC866fTdR^KBTbDa~*UGR6}Wt)-O8 zbF(|oQYvqhWgV4ump0HcOUmp@2%xgDc7eypHV9(zbLpvD@w&_D$twaaWxSDg7g1h! z^c@t)nTKmJ{#ZI)p_Kd+U%|O+i${L|7?2s~|bqFtEL*l_D~s zYy8C(v{KM|lDxq-F1*xsn4^t{k)WusQuufol{NMf5T{+==JvZCp=!X_&&|v8TqvXAI zNG*f3a#agakF7S97J}wLLtPImh^I!_j$>nw4Ii!XSlh-|k^ZrO^xSImhMXR)*pDKJ zs<*8+=?)8EJ*{jZLbXZag}dwmwFyRTncB2t`A}5J8*budOn^nDcmc{Ki5K?VTrb+X z*M-y*kUqBD)JC9l0=rag-elb)9n+v!8*b)BYiTuBJ`z_S`)y*@HL;;aAU*rqa8sW# zCSF*@c+7Za&||}m?Rx}I>6Y~mPYvvyx!a{>+GaRWrjWJQogL^QC>Moi1%2?4#eyj0KV;V^@=n?Gt);Gz3klaz z%MSw4kTDxA#0AkXgczePQjGF53qm%=VP-)XX@qbXEr{5;8Hi=8J2DDV?6mZPR29oC z^v1}81@TKQaAV}^g3?k7EVvtR%<1Wc?0kZ-B-08l>?kCMuGqT;m62Mg*?A;~)IxIZB9tlW zO6m+iWaRNxR*_thJhFja%(e@cO;>ISp+f$ffaMUhub47e2$wgCEA!EDE$%0%c6y=a zxz2R1r50Kt?UvlKtU}uKaLcJ*iS5Uw(G}-WTgi5iwq_Q&t}L>Enr5k4oN_bJKE8IT zGm!5(=*R}#u;#hW!Zoh~A}zXh>9c?~Oel1W^mZylI!1}hu+ixlHsufkqpq%q7hh?}6V6(m zhE@rxot3A1@~pg|;$kD-**V06T_6yGoy4gQ=?Q|GXU^#f3(xbY4s>^#eZ_T1#Z|<~ zOHF8slJim$>}!L$q$J3_hSV>DE4PG5f*xiO@*dSAj4R2D=7$mt)z7jCB`g@uZZ1%4 zf$hw-BMAY$R^>}Mbj|t9TTke?=CVlcnz|-^`7&$kr3xYUiV3C5XuMO8jOkL0q_@--))`e9l=%{k(dB3h7|AN27{QP=D$fn*WM z5Bc)xTFRr^g>lX0u^z+umU_%2HNyOl6dGN$gJbPdiahP)yMTbHXk3{E^J#YCr@2g{ zs}!y~>kzAu%~>R?<%dwZ)Q9SCAw^1OTt+KjHc}!(4WpiWT{;ko1xGO*NW_A*n3f3I zSc@6xnL&pp75!Q;7*i1Edx1iXeoaWqr29xB;@nGd;TcY(AkO86d~rf-=X-+*f=cIJ zfIbMAZOWk@q^m2*L;pjbaMo}|@bh$E6n#tgG@&j zZuB+u9JxEqzT*2+aYeA}%sDlm#)I%q$*0jE^+OLsUy%AmaOLKqcd0yX3Hp|k7ezlJ z-LnKTs8^|eDP;gxS@lcKj}`z=S>;PPbj|lOZ}cYBCkdmTbfDSeg6T8J!twCSpnl{l zk4J&DiF+7bk<}t2fOF}Z^11F&mCwDO%StPdzEAWSz;NYtS@e-w5VS`W^Gi|J)W_+5 zvTUf&yKSRuDUaG3#x8I})KF4k4t_%1on;HApGqeuVD|7HEow#xct*SU~GB zk2a>!RSL_3Xy6p4K%N#8GCuVUtsvw)b75UsNRiTML_`a%0(nRwJ%g;z%>42M9a;Hh z>ac(=K03i?4e`FWBdM1~3nA*6Z^N<`K5Kow@^4@fX(`+x-5?OC`cG+k;}f5fs>EGMBM zjd0=nA;xL_61t+x&d)}S)3Osm70X0g$mWuT80TgtfNl%$IvwXSoTC7+3nEVEC;;q2 zh|@U=5W6tqbdJVH#v(Y6h@~Kf&e3>DEERD&N8`n@G@OgCkrhj~>9S&E>rq-JV$7Liloo}|Gk{~N z<6MSwG;ee~h&Y|2d8KiZIGv*aED9q|=V;!@I7xU!9Nr1kIht1zCmGW@npYer`4-t{ zl5Jr;1G#dhIuyjIp80W0)i;4K6RC+f%Q7!c^2^BMFq#*Slk74OlH{U!F@$t~E<(b% z9v)}u4Ue~Ei>IjjhQ(QG=OLu~hQ>)w87ic5&yA<6YYwA%=foK=`)nt)uo}~y zL*f*pdxpd{J8dvxQFZ^|xNfJNg&3!u6%X2X3_^_3qIm=3gojTxAAlIA4Tux|J=JPJ zoZ!T%o&8me()z~*1wSWFxc9`RA7XrOwu(`$vg3kypA{Fp`>Z(G5_Crb)1{X%cEmxBCd|} z60tlwh9z?Ih9`!rBR4TDkB(U4yu5f~s59LFsT8u zDuw^42c!w zo*m22Jtvk>$GNe*Tsnrv;_5gr7R#k$SS&Ypcx<>ja%01C>4?S7%Zv3<^$fjs30$b%hcSzB}NkbHeMR299;;=>jJ=F10AqB9niQ59hU$XOq6HV zc2^b0atmVsD=etU1}@^rq8QK!^D1%(Y!{egxub#9S5*Xrw?VZ)Ba2s3U=-HY4Ww{X z9l`=6m+N%{D3VuUNAfF!b^R$NA(6a_5b(r_xGW4%aWESR7;qISs0{;J9EBiYSM8oR!cgDVzs2>Jgky*48tlp94jInxmXeDh{ex~#jy&~ zk%!ffjs#Y{eE9PCBn~wBlH$Td1ed9?zvYi2z;C>TSyfg9nC${UZv*VECLQ3r`8qCv zm6um2tQeYI- z!Iuic(lH1NkX)g|i3%c#N;^_e6@(iVq~uE^Q5gb)IT4qK0b>qkqX0avB87EffSIEZ zV$@It(TFl9Mf z3C!@3wY5mB0wU@tic}Ny=-rWdHJC~7jLfUewWj}fI$J;|ry5!L90Iz%S|tEm+>X?i zaRmQP$E#Ex&_fHHSdkMeabm?ztk8)iIA+#X3Ez=+q&kly)=0Z92@XGvtV%Gql`{Ax zCVzl`0&p8Zn4^H8Sqq456{Tr1qT;2B1en@LAB}i%4KTG4F9#rJEs(QS6bPRHE~j_} z78<n#n2)TjL#zsmMkz(A33wA>4JO{3BTcn2>j(dx&Q`oe)yOK~5LoinD#5HR zvLkin95L!@^Qu%HV8IKWSg{iu;lxUuSdkOU=a^YnEqq7Wk(vZYtWkDd@;Ur8va&Hw z1#PkgNQ;bk_*umm=a5SKY-5aWNadc1d1bK2`bC(qUl(S4b}>S^WG#I5F>WQ5``N~J zD>+lSXJRCSx6$iJ8oNG+7$JE6C$K0L(x*$e-uf}>O!s~kv2{*q|G3y%>kYd_|No7x zv0k@j@R`NVpp1UL62KSG1Qyi-(6Y-k8(fXQv?i26oxYG(`R0tIPpAp+gWcae(*LoC3XA0WQ_?nO6xjsk zQ=ect%oOaff=uN4Yn}nDPjD&*=wU`zA}SdZA~T`-WY$-Jebfgt&0%KF`ig$Fg(i44 zAcN3y$c$NEvFW%C)T`ZT!Atj5&}qPa5`JDfw{BnkimIJR&K)EqUm-JReO3R8W^+rH zh)Z^I)>jp~o-(0nT9_T5^))6V)G5)bD_|k(WHN4p<{K;;(7A-nob@&RtD34@&o;PZ z#;mV_&PoyqO=2|Ig_$wy>kc;(9CZ>`B0|*-s$Q6}d@YCBiCJF<9W}MLHV88m`E~tk zny(4&h!Lve_g&*MqfX)#h<2Hfb>2Uj$ov8kZk9uKa`tz=;iYr?2noccGak6%8~WEZ zyLO^g8D3K8@p-p@L$Uj6FP%jDB9ld1jC-!_lbPjEb>IKJZ-C6|wGGTFv*nZ9zsV)C zM*V|Eh`Q%o=W^na#UsYRa>7JeEuY-!;Xvm2)Njpqp&F|OJHTz~Lx@Ui|h(>|a9c%6beMW)U*!XphnV=~sJvK)) z!0`zequFa3G29-b_9ze=#Rg`JhmRKk(WN==8c+h4C&)cg)JGos$d8z#OgX!#%k2qP zVD|*R$E7(o!?BT)fd-X9rx=^mmJGuT)nm9Vwos!eKS%(&9WiS06mj1-z~ z(16H*IqGG^q7t((}7VyjBcj*Cr14ee6<0RgKbc0$(4WcrnsPnj$aWnrl@ zKPDh$r&P-~H1zSx#8Px!iq0<+w=cSMoilCwqf4pYYY32ui`sQW+X25j6SI=lr&NUO z*s{yT3fZMv+*2~jKvHBM&16%hr)1bhm2wTxx$}Kb zNekWanYQiYHG18qC{xO=9~L&N7uk>%ErLX?EYC9Px2vg!$^f(L=UKIB zX=h_oW;Nkw%yR7n?P|kks1rk%?wd*cbhuBUxm5p>fkj#9j^7W%Lz>J+vuowr=9Q}7 zU+@3!H9{#}+dG(rp)u!%YxHkuFvNNahbDWWJNn@5*LWBvk11tjq2TDeuDh=Voom^7 zWn+HH<`D#H@Lpb8q`*9}Q5(6bj6qcXZB8E+_AdU_fKcso%V=SvRP@)GaJ<$E$b0&_1U0M=8Gcvs8OEqc;OfcGSa0n8pRBf zUAJIDD+@>QISLwkKoBE&tU`=jqV7g4@b0r0d5%G-i&Et-A_2`w8#7TJC=nx}Inrnw z3NGFuJ*gugN|&gUiKH~~_CW1dc95#|hmB%$2rIYZQrgKz8cfNL%2<*@b6n9V3Fw!J zxcT{%GG0_+|Etk8ailnLprMQxMRmfVlB&z1uJZ#ZMfE|W&&2WLOG>TGMs%4jB`Y1< zCt3NOhewLj$J2NnWU-%cpek9S!s`E-<`^$tHFNcq@uHZS)38jRP?Hal`LQOI@uJ9% zPm}pO=?M&pOo{}|G?>wPB@f!X52J+Y-#~SWstH*qFsnhw>$rcxC$mt<3}8;{SDkQV z%VO)Yu})pOj|rRFE?2)?_{r!{7LF|sdwHg2{4HsOC<{lC{h3gxg3_};YSC7<^Q&HxfZW4f3xnooi97)|Kj1D#`F_0||6 z?3@mB!+l61kC()7Qg)7N^GPDKDL?jzoSM^3TLIzt-t=B97;kfg@VHCJmalh6**1|X z8XQl}QaE-^r?ty{(v++np*ybV?B*nE$H3$ugywXtJhVK?vn$ItxRkY{s`&AA_wfRo zzuw_xUgmIo4}?h;lwUn=(J(tK*mzw^k1Qb?U?=SXCWPR4YL-H1O#i0MQY;xVfr%xL zF`+Z3e^bBagi}dw&d1Jdp-djt=;PDB`AxfQS3)FnGB9o1ND&%$UJI61(kf1}gy$V< zjuidTo!2=mNj>uHj=LpWM~Y+TUElc@C|N?u&9R>`anW~Q|1JHS|JJvJr09?Dz4=?j zk|`|!^1It-c6+Zy#u3H&mfx^BJ}bSxgXt9>Fv zjM-&2{ps#+C$aNw@R%&C>BHM5+hj6k+)}M8gzC@t{@~ia8^KiBO@F!P#=eVWqJB*u z-g!NUyj@Vg`lLPKF^$#Suj$|Jy75|{X5GqvzIelDN_SlnxEM7qR;@nTvYi$XDn3y# zd?`~97^OCvoslpmi3`o*@?Z*fo8;xefR}{TYmH8Jt-#P(#DlScDc^OhBv^QEEiC+b zP0$=(-wuSmBpgr%DEewEqcdrW2iKD!NClXPv4ewUMB#UA$Q066q7c_~$!H|ZLoX92 zZz3TdNJ?-f$@s%s8RM@DT!|N}PGn+~HDFIdP7{(P z>!LDSWwdYc*GGU+oEkf|p*H!!RTx{@!hVFgc%@5tGu&SAtZQ@L?&ty-F*$u+!vP_mrUkvJw^l z2f(~sco{GLv{63J;nkP!!xcnDyhQt6d2_u;-n?t`w=a2I@*57MeJ)K#x7(jX6_(mb zC%d2Yb0R9{o!&2{eXI|UO_v2mlyY4x7RUt8gMNq zxSlAzS`=(S?hRT6Jq>qT(3>8qRHH&Rv2ITmtR?~7q+ZIq{ZsWXqB7ufN?PHLK+yrC zKvJo+pXUv>XhHg-SH7W2ck?V>8VQ2P3!Ox*1&*is|DXEDsrUseWiRN;x%WP)SITaX zDP?ZsJ}wBFRJKb=vStnUCTqeplXoM$JiYS(2*3{cm%}VvI zQqcKJuJ7*J^Q|U#!V&B-g6RxZ!LFCrCEOn5mhb8)Se5*wN950Xk{O)Q0%bENeTk*j z8(+A0#6w}JgR8CNm5^|~^r{oipO@ZV<)7PS#s%)Cq88q6`$TK*h|{Z2R`E93eLu$S zVbnre3k6|}FiGX13H4jnUElLDtbY1K&1LOQrru7$gGTebjvqT?y_lAdmoMoZ&9OnSd!^-yxMsTA`Aj32*w1c z78rCwgbf7v8&kQ6zM_0s;TXfG61=smXAK#P@cnj ztF?;)4m)01SE5yJn+s6OAbGIVlc+=^t^~qm#^?rzitTL|=#JXQ)--H-hl*|8-g*FA zhAdC7TUD|Qx%LFjC-$RP>M~P_)I()#9lBn)lJ!TuKUl_|iTwKwmOS@5g8Cz>)7KuB zy5r?Y=5cDHi(^fafVGYl@ATYYvD3X(-k+>P^+$dvPk$RBy4ey*S zJyHHGA#O6E_M8&{(y45&Q};($@wRQo{h#Lt#=Bq z(ns`ivi7_!@s2!iFW1ZE?FTMfB$M1%!WCM%ya7QA2+u(7%prb7$nk(yZcbF@-09Wa zSq{sOYRYXAnY+6tnq^r=v^M&s*8~MEl)Pd{{}*f0op(@EWlwtL)EiHNj(07< z(=`gVz5U84z3iiT#yna0JTtKU5rXaRegEXt+6UGyI}78j1N%-Bb$J;Rus#6lVG6ph zw)AA}UF1$y@Lx%^ipv{H-a+S|sEi6R^H?4Hs#edz!isbMTXJ98@()yZ;zGTBuP?dc z>!(P(NEg+-uI;X9`0JD z{71l>50Rs{t?n>|cb!VF(I*|duYpDM{ukifPAh%DKV-d@p9~zb&Ro4ppWz`UZ&di- z3+t=B0JZo3r^~xCnwNUFpO02K2B7>?OoLvAhkw|S;q=!k?|&rpuAbKll?qn%!B%hk%&#v^{3|!G!TlpS|%R(}ZYvKhxX8KQeOgknyy9^fF^?gId^n zZY=o`3P};hOE0F2%%!3am?TM5UPD8ujIyx~l^7bX3`GBU;OJ0!7jd4~gcqAsGWe1o z1=6Dv^+<7?@bdRK=EYLSLq$d9H!$24fO~(ioZr0gk45SU+qdefS> zm+KYYS8Gl+)-`30aV=d-L8Rk8D#Sy(3e)x4dTIf6R@g6RaGOYjJZeQVY$H&=7h}wo zQg3~qdZJLemI28r`LQAOf7=lAioQ3d>IvhehI%o|ODp2{F4)RwQ{Ol7qZIE1(%aKR z^{CCS1Fxmw9j|(&*9N1p#&&>6OlEnq1Lz?ly|t)2AJX;g3Euf0R*dt8(dkTpVAU~D zJFgm%M8p7~sz{fh0mF|IHP8EC!rVVtz0LHSUp%Eq$M7x;{7mt<``ViOvRPlu^{T(Q zpfm&o!4#d)Jy`YtZd|y(wR-^3uz*H2)BA$EsfaAAVzspPkbaMw;^6X9DhG z(#bctZ>hQ6QRX!N>gs;ND5JvNqWSv`k2Iq1`R1Cx1#WlOsz2=UzLUsQ)^h(NlFn-N z%3jYG+PDb+=%Xd*B;SBaxt(90toE+vzdA3l`TM9004l>BiS(q5094rNi9pAv7O@)_ z0>w=SV1J5p&0zQ`MdWS8g_@73YZnqeQUt6v8#F!w4w-N)$SFj_aP{FLP0_e_g;WYb z!wY16sEA!-x+Miwic6DS3@3r$!+`Q!J4zuhpDt%O223i8Qv5Vlk$CZ?8d9CDB69oa zMi}M^dOPf%eZRCjV64(kVrJ-jd8SO45tTr-qj%y_lXH zdgY#Gr?{n4N{Zc5f*no=STQ@VbZJtHEf|2zNhFklH2S}uYsqcnG=GVaVl9a-9kA-; zufvrTNg-Su#1k^93Betw1L~ceQ@Z66U8tChQ8$g~f=Kh%CA~yfX>J)rrx$zu0dt-8 zVs=RBQc@`zQvE)P>3*}=PV}lqQ)zy|vu{dQJ>#UE z+I5A^3P^$5Yo>D*Ag-|KUg^r>3fnIpzbkC%uJE&^tYxVYmvkyu*nSa_Z^mR-*fcO) zSJ-Z=$U!#S=Kh+I(`&Z>xfW74N+)_M(c9_F5+w7R1tM-Ll8ril_1NomvWIC-6%Hae zK%`^VA^2}`d*Fh3a|*!A)#xM|hP+SrfO#`?e!30j?a}~-+_{ULH>3f~TcE_5PpSg* z=F-^#0i|$##C<{vQUpqz?}`*ywtp$tQ30p<=>SxY`AMM4nSZ!Iv*#MuMM9{6TyO{- zJ6!-cxkE?J49JB*ut1z&xvkU-$ZMd~m>m>Q8qH^3M*?vB(t}Pz8qa&Kn*ty(Qlf~7 zE8{K~Oh}#?czKXYbK`X7wa8nU^g>x8+{&aE20e77(v%`fb1TDYPAlYP!COYEo#yQ_ zDTOrc`<0YZXyJK<>s*dIR|Ki-RgLZ!1w;xpq>Ae?5p5W$u7fzC4Ht^@LT{}?g~47* z3l@fu%B^yJPEab>*DEL8PW7uzFJ%8mZs#C5YlXB#P>ub{lk{Z$0W`N4`m`e5{-ZCc zOhxO;le}J}6w!hqk`ql*D%VdsAnA=g7fZXv14eKDjKlC;EPrcqOGx&~{)}BsdHorC zB_jTeU2PG@KNdOV=a?ns9frE?&)Ah9)3ZNguP;aL3*xspxj$(|7M>2vpSvtYzM~J1 z#$mX0q~%3iPD%%0f5!Z4Ax+=h*8{AuxlVz7`ZRVD0H0# zv1@|#LhpP;G#IjK0^mB3V}vx7BDLidaxf>e9i?DkI`a!Vil7v(4-FooNkNLM2ui_# zRNYuwK;?6qpDqkK?_eg!3mBlj!A1N+YPyl`$c`R@NmCHf1?}lZ1~H{!2>ON;l|gCV z;lycvZBUw~rXcPDv?dTgv(O&rEGDjuJO8*!q1nfsoLP9fGjoVn3;1QSN1zBR6~3Ej z-r*UB)s>>s+>uKCF%@2W{BsVaVH4quXu6l`hm8Z${7OnGvU+sbG}K`XCagmxlswh<)$%(qCRv|Uh_(yUYMsmoRe_WMOYVtgoiVX*+`qie}p40tu#t2rH zoyd@Nl3Fb3tQS{*MiKdvrxvozMTffm3_Yn#MeE9whHgqR%@{mux~nXu5?{Atk>1#g ztg9t1tvBDYH1EpuZnrnk9O(5e>uSpDTh=QP@h$6Wi&sh4x2#{c;#=002-CA~*`5=c zRxD3{{Tjv=0G6oy-@#Jk{@jOmSs2yIMDd5G;*9^9fke*<`x z9W0?;ci46c5>13{w;ym_W;+kbNTk?KK`54`T#KY9QV}D^BOD{gBWZ~=bw?&*>GmC| zb}Ty|ZXoP5iqXB?0>AaHpgk`O&=;7n+cBC&idwSj95InPXXSi4akA*>IDPV&5UL zY`9)ZwbM98T+;H>6qhVG?s8-4HkXwAbmd?%s{n306kR4_QJV`~RM_{1^U1LxwE%7` z?6gomyC=*l2!FUNP5;mrI3?INyl!(c1)20 zMOfjJQ9mI=@chEbZ;mo4mT(+E;pjUz$d30Ty8K%&NkS&imu}1sv{cLW9d%1 zrsABg#n=%Cwb4SH>i{P#4o6El72}puoNJ#E*HoNJN=KaC21fJJ;<}SAak@7(9<=X` z=B30#h_m|-j&rGzc-Tp&IPs)bh2s&2XE>hX(1cW+?+wON9U6&Kxq3X!N!R1)%IzAZ zYlw3lGV`oByWXR8j@uGQ z?xmKq14rhW0%wlGGsWk?F>I$}o=kG!$Yn+0a75f9!g-<-kHR+zyT+rIumeXaxi1Hf zM5*OjwuGq9u;$5y3;Kp?8}%8#=5dIV{n zdsYP6mUM~}A971W+Lrzqg0>~j+U9bDO55DCk~J^&Va;+;|dCYE`o4g@#>sveZP|)N+Ya zS$dwhw4`{t(-SqsnNMcI$|L7%Tpy0x5?6hXI9jm7xrG^slN&aoKyPu<$sr=U_an!h z^EpQh+hs-JkkjuRk;@|2oU}*a`nd5Z)@Rx$ za5_6jq$?`9FXxEVA}v9-Ni8%)M;)B0(lG`aI3Hu{3y`cF7eJa9Vzkh45hO^*Z2i@Q zm5&$>E&sxVo_|3in2$T`Ba{y=IKug35|Mmx#*vbbl5nKv*Cf*N;X4OMdVZBUGV&`E zbVTzj5?%*)+4{ke>2NFN4Kv_kjIJe&!rd5MOPC3l zV{|QH7Tk}~bv9g(dBc8iLq^vU=D;NxUH6A)9&b1RuF2@y4dd(|XCQo(nWgtcS_Z*U znOW5wX*mnt%FGLAQe+sM@qBqM$IOx&Qd$PXbD3FlcS_3;xGY27JdT;A&8aPCC(ee` zpX<_E&PfbL-lPXOW{#Vc-g0hYAd)V;GXt>!aL{wN+`0jg_kOwAd&Wro$B(d47>F18&La zTEZw?l+m?>nQ&7^*AiyIWf@&(!)=*2><8CnbS+^H+?dgIf4DO9h6CWvjIP}<&MtBW z!mpV*0xoj~!MT|UZ%Omcf`>En3o|G(4E}f~J-{)u@cNW_gW>JWthy^@-VnGsGh$ah z$T4$7Q|i34V`szh&*Zdu=fnmh?}|AbGcTT*KJVPvKqP%(dIn+x;G<{a-O+iU#{I~t z?{%E6V2&quFq-XiMM52OKBY?-#Jo?}5{78@$8{LoVQRcorKiP5 zsW2Vu0o^Ylxd)|d3CU3?T}zmW6@soM%)mP^fP)|4j z4$$b@4dZas11~uP;R($wxi!)>2=36#vfCp~XT^ud%(_N9^2NI;G7R3!u1#qg96v8+ zR@{=(Gz5OpjQAJt<(N5QYHHKj@w4Gz?rUjH=fnpi@6x+DW?tBs-gGV;dm8b&A7mgl z050Li-WqM989wycItlroUm835pmv;zeHs}&>~Wd$dAJMtyxf$0+H%{i4!E`;E@xt& zMg|LQ`H}0F&&)}Pay0WlyR_AgYn3yxSsDLz8QjPBe!fmq&Xf)yr(Z;OTMy^7oSZw& zM!6SAG+m)W+Z2xuXz7}B^EVjC-^Bse2ziP)v<_(kB^s5!KFq zc5qAQgEQ`$cKfZtTlAZ?TeMrX+c?YI{7}5*XUey*47?W|qq_DB3l*}Kfmj|}%w_1k5xePmctlF`2Qhv)gy`s2=r zhx(ER?CRdW;*t4h`w|E4>T2J(;?ai&`%=%^^~ugH>z`h-XyN?+zKptEXm=QP{n&b=mrAL^}7iwm!L`y%JGtpWbRbv1)sTiU(V} zm#$c|qfA{yT056Mvu?|d_7Uo`u9c{JocGr~f%F4)%W;0C?kSuP);){!p}IDlU#nY% z^XqkMaQ<=KI-Gw}w;ty=>o(&2)4I(#|Ez8+&Ofi)j`Le}J8|x)YsdK)b-UGh56-`= zdjaQPsq^8w7ZK<4uj}?9{+qg&)p{!u~>m$qBX>NVkpOn4R-1zWtA637d=9-6xB_$awaA((!^-nIEKiHRg*3M71Z(6%z@edcY^!H^bg)6n2KiyFa?VE3K z@S|<&YTpJ5jD5?u*QjvGN(#I8Em^v@8j;R@OO`ybZbv1e=*mlsWh=K-IF-~sBQ($uhu<-^TE33aDJ_BCC;zc zt;YF{y0thPq zaNSFYdpq>uI&^6Bm-}m}NB?}k>d^;ZRy}&(9twQ`VHER+7#_3&N5fbh&P>Vo;mCO*$+HuCp|YeImuYHs5#~%%Uo@)nC(k?EpwHz z@{#6TFWGBZtITKb_a|kqGM~C{xR0veDs$QW!;+GWl}l%z=S%BPcMkO>4OrFv?8Emp zpY2N=xTa7U8_NjwY6u>K5b7vv<#K&hxgnv-h06-%zvnkJaq` zlRED5G>3D?mzkWpzs%<}2zXZi84U&V&7HNF*3H*;s9@iAwNmZbpjN8gt?gK$0@ms} z>&*G{k^s2}6K4hV?UsBr)EhpQRs(2u*P9pt1qgPU7sX6Iu=}LO{_4qfh{EK(16r9i zx9d}Xe@A2Ev}x0)-+AX|*AW%w77xAl%QH=L2u->YlP4qm)#wialuf1-YQ{ZsYN*0dinJhyVynzidUZr-wO`_A^=&+mP4-~I!y9DMEdH-7Td zpS|_A{>#WO!f!`9^taC3a7IdhudacCxDx|$N&T|=C+k<#KU4o){mS~)^=mN{H`H&c z-%`J=eg}r)uKL{=gwNyHTmO2;t+(BF`_!p-U}@B+{gsx+yYKd`jQah-`+~Fd znV}iMX1(cmE%VjZa`BR-SQwvv_PLd-*I>%uymi}-UAwU`?tA&bD_9uyABW!vy&ifk zbVxgR=9V)OTlCmJw!>hEuEG#qQ2$8%qWZ_`7uUDigY_vjT-}v(1J=$>SUtC3{oIBX zbO(lOJBH{k`X|=1F}>YSW9ggk;0Q~BInWoK^l2%7jU5@Ok6=_}`XoU$;X zKcyvP0rOKnK5@U72;=*0`XqyMykAV;y$0_LPY+ECHHPm9O%2~3x=p(^crf&8=#}t~LI*C&VNk?f1kNq10X`S`dTJZpd-#-rGydZuSqfs!v(?VDbvD@>35_|O}Rbe zw(zaiEulk^gW*@huS9+nJ`mX-hCU-NSudVRWjTEMc~{2=nDTXml}A_f9yq-XA5=N~ z%|)m&<&N~Jq1)4LOT9IGOWMsLTPGptB=mA*U-+f)i^08TQdth4ey-H<397ucZzbr8 z-_OUp`YMON1qj}ndQ14`$PZF(3QtM9F?>VH_x0z)dxE>eyF%@uouM7!?V)XG=f z&B0A)a{g;F`1{O#Dfp_fD3OYDp?yxfB^8>>zDb*+->BW7e_y{|`=0(??K|4H!&t2% zFNB|u>b)aj|#!17Jjmh@d#d&L%OTlyYz%ck;e*7nrh z-MhA|-&C<91@FadS-)=cn4O(FHld4bDhFv~yR|O3(%OigzALoZ+_Wn6jJ2Wsd8<9R z)?EE`c$u}9F7)S&XO})2USzE--`llw>C)B(DGyjrmGAA|@yNpaQ}4BwmA_={oIfk= zE^8?|`i@z5Q%s)7(__F4eA`nPr>}Gi;`H@qLEQR&`1LCz=g>RW!Saph{QqqJ_BY|f)+-oM z?fP%bU;iTVmbJfpb60yu$6IfvykWgqzNLHTkKcGTb-(p|`BtO-^;gnfw)T{7GuvND zMBI#`!idHCbILo6PGp5%W63f-{YRlFDe;*{?*fST8wXBcU^F@hEbq> zGpL+;>R;2ps^JZ&$d@~>{`z&_pE_qi%X@9lJ@@Q0Pe1(>3C11+k3IJ2q96Y7k%t#9 zSn$yN;b%yCmCLY}KP*0-?9OZX{gQ2`BRZ$$U*`Yt*&Vx1M>nLU`=Q65*`n_{t;z

IBP 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

JE~Zb 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 literal 0 HcmV?d00001 From 10d3782938bec1fc1079c298844521ae30e3974a Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Thu, 25 Sep 2014 10:49:56 -0700 Subject: [PATCH 735/847] Change name of program to "Terminal Emulator for Android" --- artwork/Feature Graphic.xcf | Bin 173306 -> 244790 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/artwork/Feature Graphic.xcf b/artwork/Feature Graphic.xcf index b9675ab8236fa1206d1c50353f377ab54a666d75..b0f06f414edf757868e14bb029ecdac5214d75fa 100644 GIT binary patch literal 244790 zcmeEv2Y4jam98pwRG7%*Q3pvK)oQi6yQ*7hq)7~5>@@~^y~Y`X7lt9&*31BdK_c4( z6AVKzc^DXSjx%H?VT0{8#;oyrdF!{&`-};Lr0%ME|GBrSx+SoI_4mGg`#t!++xOi6 zoO4s%I_KUR-NUzzI%Zw#$k&f(J>saNzuC~xAR`Iy;D`R3fg}j{nT6zSXt-dACzSYq z2R8^iE@_bNy4?XFe>qPLq_^a&Yrk>)u}6I)ed0HdKjx^lUp+eg&11fE^jA+j_V{!@ z+f7QMt*IiXwk;&I#QTBVXVb_UnvsaN5GjJ>K_Z>{|9-&>ZZC-cKc`~CLk$g0v4(~P z7d132{Vu998yb?ChKAhl8yd>jHZ*+7XlVG_Sq%+GtZryH>cEDEe>}3G;k(|3hD}Hp z{d+^h4X-ye+>XB7^>#zU{qq_c9)GW);W?z=oz&1U{`Cey{9S|KeV{>T5*vhBuQmva zkV^l%LHNR=2I0ty8ieCI8-x?P8iWlq8-&x5&ijuB;j$|mgsVQ^AY6AygRr&IAl$pK zL3m`KL3r#=q-ulk_-dq!kzQ{Qo=DMnHVHqpD*yWOFMs`7>h*v%vG2KEd-m+zn|je_ z8IvzO*Zrc`GWNZ^E4SB!^3V75AUF5!$?sJy^QE1Iy^3YNy}L-+YrBhkkiEjQU$e~m zjRws6ZT;`u-Qzo-dE(Y9FaE)qGsh;*IP2_l&->v8Bs6<>W!Dq8Tzb}iQ_xwvtIuw^ z=*;<3z?r*^9T%Osupay8mfgm#hc8?_729P#bitA-Q1dQx`-MxVKux>MXD(`;0yXY3 zc5XRyI%Yn8QD}--aF_AIEoV-_UJLFtpSm<$FGkQ~XU!d}oWAL#jqBBsiS-*co_xxt z?_*AF>x)sQsc>$lO(v&Jf$jyQP5pm)IQU>DCEGfp^YFi=CzYZ)`X zf7DRZfX9(Jvw5s~@|TAOUFd~ohC-75cJqYDJajL~ll`5_|OB7w2$rq3-~=0m-L z3z;V!Bm!!xhc>Pjmr+%9QX7^_Ov-WSt_7D0w|5<9!2maKuf6!OQ_k%yR?8oC|^+VrWKjcS;JoP{T8lD06 zO8)+(U%X9pAY2tADp<#b95s*9NbcHCj34n+GlLmuuLM zW{w)iA28tU3y{=rpn7i0sB!9%gMJ5j-WqEjHBS1{V6e~6Wu9*yt(^L$W&QoZKA(%2 zM-EE;0T*Z*HIE;Z>wv~l^OOTs0A5=Kz&G(i2m3q(-~|Ydnj1gm1)$b|V^`Dzfl=f1 zuMhelqSzt<)Ov^ra}ubBHXcY&uurZ@ZCEA2#7u!kV68|>;!@8BMy>D3uwTJG(M8OW zL4pEPA%*D)^->CxszJuc;2;;XTrq1z1V+7J*sp`nph<>NH!uk{A~8*jXbR%%_tzmZ zlbMeA`h9gW3X@^TE5WBRRU^wdlfM% zH2F{L_gDOP?vC*%9=_)%7o2(OhMDjv8&85mIh}-N@2EU+&(-H_+;0jxYkT$KE5|m> zp90R@ZrnGvVc`_4WxMgjwz0+2Fmv1Jl6vTu%}<#3jxL=7H9cWIG}bx=YJ9?YeDj9s zn0fbDXo^_yapTb|H%!OO2hI*p6EpAHICrG-y%UaGw^lu7V(qc(jz8f$-^HMR8GPKj zdd|rBx+DJK;MFUZ@#R3^`LjkUCmw#lFx(bfB7~hebHq4mf4=A~>leYttW7N=#&^Ct z=%?3!BO97WjN?8(803ovGR-5E?|gpQz(9~M5r&vw9h3$FF3>b$etVf*2Q-eDC$3Qe zum$t;;G+P(JgA}$548gzIAR`mpcjBz1CCfx4+KVx?|o^R4!5*RU0_>2fY5^$hL z03El6pdf4$tC^s6D<$}nDbTUQ5|ff#DQm3^7ZvOmUBq0wjGzFAUJgEts@7uVQ|$st z)gY3pLB`r;wwUFLS!+dL)QjmDRp(S@t(BN2Mr2tp1s@8GaKty@uR~-eGad2qEt9RK zFd4RqVu-nRh(l}-^-}Ob8jK6!oo%T24mx-9wW;d@4U@j_y!vLvOk2JN!`t`!Zp+(X z%=zwHIm^>v_)mVb2f6vVck`CoV9tKCU?~lzu(?RtqBmIf?RQyrvB9!yOvDg z%)ln$RBPb%O%12MxJek?i_)H7U-+X-F8%T4S6+3^wb$Kn&{!IO;@}jy|{6yiCS^A4h3%8eztzp1Z@QHo6kODf|wmWkrr zK*u|) zd+$wb6YVyvl7yq1IdGk5na1rBtGBUim1G>>Z1hf=FKVHNnRhvug3}t8Gw)KETw}Qo zyNAzfu&fRk{R7uYmUR!G)j$oo&p})x0Ru6(2c_M=zJQ+Q<(sd(`X|?3f5S~%w%)q! z_B-#o=e`FXeE8AFp4jo!)6YEj!p_}$#hR%12A6?YF2Nc9B?VZ0-C_E1-c(p zka7K*2rO8d2TTM;luwgvuD6boncRHcqsdHezOHJDjj5VyQwk_vv!Z!yN@f(*ENfm{ zP6EYSlQf?#Co+n9DrkP2D`)|mYeB<0TsqnMs*m;DLah^h*OF1V&Cd`|ui1AknS*V9 zHVzACOB=a^#<-u3-Jgw^cfJp~i~2KH7jckeT84QyYube*%ebc({SQD&!a)*JfbJzI zSem0VEAR@o3Qunm8lFOWa#O?h9lwTwr|%PZaM(4_V}otK`IfD>ZM);{d$4PG_>sq+ zz^*}jR(VE#T7F7?QusvWlNtM%3x<6T+H~sov7I=ReW5=8f{T7c+X=p#xE^)_OHR5? zzSXx?zD3wV?-adf7C!p~u$`F4DE>YQHb!qtkS*=~@J?_IdKH59OCj0ZId~UHuCX** zmu)$sNG@2Kr7H|;EX~wan@be0d8Y2MsTqtS%P~Wz&r_t^0*cqttoxW;W2uQz#BI|3 zHn&j^*tIl5!}_v#ih1(ae5^}BR6EhncMg6w7(&c`W+rauMkb(JIMCYI9o*ambbQOT zF_;S09e+J`7w?!)7jcGVx-b`ZFIWDrPW@RHGJ7`Sdz)1c*U zBJ7jKX?xV&@-B6!@`Ccb@|*(upggTSWjwj(6P(S%-FK^Z`R`QkP;OVYDYyA<^=!3n zDPTs6C6BI^R5$4*MK6_PmdYhbr_@^#S?VhZdVk4cX_L6UWR+1u63XBpiRIps`&{hJ z-D$hkUGh$K%2QFEGR=;jX*XJLNlqx69kq+mu_Ct?DhxmV)Lj7Bx>%(^NO< zno=xjGD~GmDpKmzM3(wAq1dlkENv18H9R3V33{J~r>Egbvh+c2_vsz-cKHeAarrU% zQTY+&Vfi8XLFEDIe(^r(6V6|ogDvvSzMJG5l^f*imFs-h%0H2>5w9-jUag3IpN1Wu zlXP9tO4!d)D(ezUy}GDT>cjr6U$VIJ)ylrXPIQVH{oB$qJvc2Y{Zu9om@kUXWL z?kyGgc9gzcxrb8Oo@p5~EkB|@tURPVC_V81A&Qw+Zc=abvzb<|RemB}gV`=(#kxt* z`ihjWvRT4PFFHxl^djB_mONUq=+z3v53)krcZm3KBi*CiE#D=>CMvLrGHjx>Rl4O9 z&R?B_tCg#KSIV2^E9A@lKb9|3E|o74FUB)+y#RRx@&uF|PXHgUIZ4rV4SN8VJbF>{ z>IDtY=r8QGPTixxKdARB_o??P_eggOcL{g=@7uTCcI&NMZ`rct<{NLk;fCw3yY?ra z2>rh~1$cwiYm}>lS4me2oBw#l<~ zL5bX&DS6z&kV{qZ)eqg%Gz zeA7)g-f;c(SV-4g^S~!!|Bp_{o6{eb_ZszT^(y(wz-H+R;qpK7wRLfAZT;Z9b92Q& z!AU(ue}Ph==w+!`^c0)~pHa|@3QO=O1^5=0dW&L#5_}Fz@IwVGufO0`gj;0n(`4)w zWVTNeuN8hGTrFHBY!+&C8cG;zuVDVpc(S^5sB8YA5f9vVF_+sCWVYG7kY2W|;sW~lBbW)GzFH)i-mvii*qnC61qGOkH z^!lJ99m*7=uSedm+U?vi$Y zsPw$C>N!{3{*?IPk_*OOJ?oO&pWppq?JpX8?aZs5`7o&^W52!P!(^6@eQ5Xp+ll=9 zdFS$lbmkdnjE#<-e%fi@KXuclQ^b=ePTII}!}<~Yky&MA!%3S?pEcH|jqAP3Rv!G7 zwHs%QCA5k@u;v?^n#am6a4{ylZ9 zs97KSYhkNLQi7gmxv2L{$t69<%G2}dA9_yREnM|4S8l$7t#Ergf!zd_IHvCGv(7y8 zjImjx<7fWxlB@36@yt$X*M~~a8?Byw`L-v;50_jp`s$e%Z+mXnRd~bxTKkJeUpwQ< zr$0<;$>?t{|1g=QqaWJ+|9&FpSZ9l8{TrTB-3mYDh4L3R21c_ee_@k3Z}l!H`-G$SDfOdO#7O5G+!`RZ}3pocQj>2@$l2s(L%vI=wy~H-@vkg^(-rW zPaQ25tnYrbU#nPPvO+P>a|Tu8&5m?q8sjiWV^KEL#5}9R8Rilbq|OiF1cXj)s1Ie`{?!$Yk$$mYa1?n=)cfb3c@0@tz3CACQ+;Qu~V})bXqbtX(U3dKV zW{tELCrbUpYd-&tbu&g1#fsLq@{1=lkEDx+w*1JZkxbFh4?i(D(uMqxb%Bv=(I_2q zqJN~jXzGXiM{>xH^o{fs&E+R}Dbv2_9myBXmFql|^?idfL;L*q)R98bTys3jhS#yI ze=W;O$EYL4qIL8~`?c^~1`y1%@jw}MC)RYvHN9O7gz$gwa~oBe1X=*9&^`QSP8q$U9>Kc{fcK;9(ew= zF4z;d*8+QzGv4=WZEsgO66DU6!u+`~z6)ZF|GUjiV{ zybpi8Ppe_ny2_!5=Z=Lh+|Z zwS0W)84GVC#&J&I<5N#tcw$VKQEXkjQu*b}FYf*=*2UApQv&`u>#=vSE}k|XeS+4- zQ^xk4?_gcvpJ8`RU|l?AKKV4(#Z%@JFJN6fY3_IvE8$6V`!89x<0YQGj1{rN`UU&_ zC-#d41BCw)vfXljt@~P7$vpeMW=0qz=RDB#goP@rBkOsHi)C&W6w2OM#Ikq4%`)R+ zmYLTm4Kpn3x=((m&H(`tbFfkERdTPGOyG`p8KMEBZ^wZ9lw`NbLBeH$ii@1SwWTc&3!s18;KzVZjdTSFD- zi-VPZefW{8&xVcl!@~-y5C$uHCcXYoejCSNfcutB4E7`w1CJV0aqlv-KPx88&;L0< z@=sCkB*?4F#{0UG$wcDh%2bBJ6p}*1e8jAAWT;5X%)z{zG!w4{-KuQ7H%<6|)Nlpz z;xaRrQj*q04OTG;!ZM>jP55{{t_+#W@?uI(TK^01uYylfM?DZXm!%o}t7&k5T1rVt z^NuPy$;8d|%RR%=kX2GrV$wX3hBXD()s&Dl5BZ~m8|#-tK^!vmv;vNy-%`QU*)5A; z37mz>zVW;1979iYt?c#VctF1j?qCxOt;$eeXF4sT4@PF!B%Z0u^A1-B!J_U}d$zx8 zVrkes8G6;B?sQruwV})Z*Ho4{)GIsQ)%AG#?`#aS(R7k->f%1TH6C@@4W?=sZo%J6$D-nr$AUFmenMJ)K zmZ|4Sj3*Nhe}DS=9iOSwlULFRVz z*suz9(HzRdl{JTb`*LhY?Cxw3Z4RDAu^v^~uz(KLP|208$4HVMJuqA%!w@&fOpYcR z7`vKijL>Z?x_!wmn=uTxB^l;@A4YdA*&Q@nPQo296&Sm8Ms^Z6EoERNhd;Voy^b|w z!~9lcrLX_+Tgd3U;wqZ=_E-CkvF$#@&5q#BOmZ1C#y5baI5uKVx8sV92OyqQ5&H>t_z2j-w2U0~5WOcr144 zo3`o0&?KfK42+jM<6xcSSWBCw44S@&Pu-4_5`_ z#5m^H$OY6hG?=(VHJ-VS?7z(j3{{quQ^}+XMw%Ia#xO^Q14HHj8AUWw?cV+l*?zKV zSRTY$)&&bo?JxT=nSRDGMuvy|V3lMxMk9A5nSPf=S{c%k;|`3KfXXb56zWll>gi`q zNOi&l8_P7Z`j|sj$>VI*llTA=p9-T%okjG8o{b2#kV(O_6k$xLlLgcxC+cxleLuwOVF}`>y(nX`)PyBo_aKDpVVT2a zR4vv?+wiiSj4cok|WCe-)hAPY7+46->F=@FPV3CS<=)L7;^;g#) znog1%W0e^rD+uHzZ1tUmOp zACsMT7^Fi_#>zV&IZB1i63}H+Q7-FXX52IVWEC`4#C&8Ggz*yK18u%#d$P0txy_p0U%(lA>nApB9DPhE!Ww&9! zy|+)8akA?_4cL)zm7}2$H{X=&HOVaW`Y?~b=VkML8iVAB^4LFolTBy zN^NvkkfAdAtzr(070d&;C?DvgyVtiFbTpv#GO^R^|Dx9lAd}Dc!~PmvQn~e!`aL_rCl`$=dh!D?i)& z{5?NfyFX+4%98_Me{%9&v(cI`-<$aT%||Q?K*v`$9^YrowkD0qKRtZX3LhYE*{aUB z4CAMV@zMgzFkd{(500no(9OMSS+?FZ_Pzen|1d4f{KG%}>zkFTiGM1CDAlW$MQft^ z$2)&;JScd=~l)@`&31^c=?;1_VzV)eLibFWY~ zo}m*J!*PLCw8eq%Oijb-=~T*l`Vu&5+48qakSDzQ#8TfF}_ zba92o-irAj>s&_FxFMkvrx8QOe7<*dC$=D@QZa6bXZm?~_oOC$JB3BCb zN_h5v+Huus4~Zadh{rR1T!-RKy=|E9yo={++?t3dm_`#~Oa-lX&3)s$F8IRAp}_$b zE5P5|Vog-W|NT}(p`zaVdS_Xa)$y0EJsj*t6s^hGR>iEmx_N&pdwY%V|I7Fst2+72 z0SNFyz<6@t^haNMyIPrHRV)8?;xdY4;OX6mGi&-YBNyEI(mO3y)qMQ~M9^u%6=4B8 znQEuDe2WNle4pvZ1Ag^zs)=|?!c6`c12*nI;r4%d3m;RA$|HFC#H7O|W4=d2%^mU3 z@9=Hdocs={D!sHpVqDX>L5f#9k9wi1V!6J$np%~ou;sXsJn-kxefxL{wIKDfT@*D7 zT{Uig`W?hwGH#p3!}Cpmc_*qhpIc2#M6#m25h>ct&*2M)Vea`H#cinLR-u}}D$KnH z-CrI~u^fb63r^ig@K$dWY0-7zJZrvwB+atWE5LzIWoP~X_HVvH@g0m_+GJU*$2Zbp z-|Tu9%%A-|JwO2!n<>s?C;kIiuXcW#n6#gp!n|M-)3jrVt-^Yyf^BaikhTBw1($}#;toplmoqS9IV@dQjx z)%-&iYqJ@Vl=hAP&$~|j$HP{ZdNzyJxN-Z+&mQ%I-=mF5^FL0AclJ_E{yxDHs*~^j z=IOh){Q^tgeEsQvdb{GaCjWd@A)YA6=qCm3s+xv@9f3egYvT4IUPG?#>w{_h)BUh3 z7G#?ccxgW@<6mv0N0VX=c>Cs9mB~N<{oNhk$jKy<>8IYReO8y4p2!{WrL|)_O}x~5 zkNVuQP7etrHL3$QUQ76ZDyEFCE5SGJOeX!r?dl_SaF^w3T%Max>`RQSBxiyj=%Xi z=6BwE`&U2P^W5DR9=*~F-Yk8x|7&$_=caEC$hbv2Q#T%S-Ok2g5t1`?+wB}GLSTkY zu4eBPS_0mpGq>~GpK-VI%YXfo?RNGaDx;DXox7cLzjf#>@Z>l9-hG4JNX5;oxO*u zXr)PqQTUA9Va6}#u?u>uU%vanzf+;vs2k%KrooC`$~_YKiQZ2Qd7##)o4@;#QpckE z#6jKMRjh%|{buv|cnzfM$iY%TH=mBxgQV`)%@-0ikl*z7>&EWhnA?f1jnEH3)Oh0v z2Q`cv%3Ro|S1Z@`#3bG?!??ba#Ob1Q#r)&3v6#piRpa_tk>8;9&SkH3R4z!AwC{U9{k<&Z~YnfImh+$kQo9vQmmRP4BuSub-u*) z1YL~FF*6RwJ7L^+{5IR~JPfx%J-}JlVZTRX&wlek?s-lm^it1l$f8QeF!|INb9llUGjK8{X9L-4`A=l`t`!6XaT4t=BaFLiB|3T za6abt0nTN<%H?9|_A>YLL%O@KloM(5eg|J>Bix%-ap?M~6> z9k=I|ly({UpNjd13@zCpLMQDT|HGY|zJ2Hlts5KCabw%^&m4K~zi~v`}I`XagRD zktj16p~RffHHRkg)Vr`X43w)S+X?;eD^_J=UYNEbYinQc+MoRNjo<&9>w|v(yW78> zm14dQE8PS=D_ z(sJ(Ml$sZU5Lp-7524hEFa*3r!FKS{a$ofN2l*mQTqBGO_eSR(c_=>wP4h;q7FZne zM_&Ugcvj8McX6wP5wO{gbw8fP2fH$W+rT()JHz) zv-C0tOI3aR!cN;f;i%#xC*F7PFulvclA-_ZaK*6_;Bk9x?$T-?58Dwak*{$j1MIN? zd?sEGlDZ#~$r>1B3&meD_VhVc&@{F-LZ9rA@y53tl+Hg~*jK9J{Nq?fn4k=alWn2n zPu#JGFfehAG{~?puaw5Wu94Nk@h8@4R|ccPywXs@;Uo%N3HF2?e{8P=Q|9QXcm-=- zDISX#5Z{xV5!)+W@c9*k*ei0(KL2d|B7$`iB9|PmgzXmd%Ca}p)50P!G5a{Qil)F{c6oT_aVQ?btpA=vNYhuwA1B~UL8>;pn|YL-=Yckf~_WOuf&+0{4hijYdmFsGPJ!Ct;Y=aN?)Mo>9E;>=mzG>*Kn_NFf9PJ zJoV%VU(US}3(|})CG>^eJ{wB3bo=mNgnb4?*D7h9or>UwYz0!)mJMujHSaf_WU8Xln!p9gKi}#h>NKscJG~d!>XA4OipEejGFXy~YrIldmSZ z&)Oz&%~T&WeZ8An8Z4=dcr-|I88K>*l(Jq@%ZOita-JoBxu+B; z=SsnHx0@Qv*%GCua#zVh$is~ND}e4S2?+O?E{UMiB?-SN`&R~?Eb(6jbfUz6RnYO0 z3awbliy)8m+f?qrZ*#f5YNLf(wF7L^oMURYAG9ceBvKu+~8D0z@K z+IdrXe#y)5dB`d3X};d3S%}IN zwSNWB5luvNF8ZCXhcyW_{m$1znvCC8`&R+IRO7!Y=p~v5l1nr%eiysH^YulV58Q?L zU7#<}{Gb~C({E#`L#J4Q9Xjn@+I8B=wd)i) zF<5FtPGz14OHrL-CI(9pmN%BdI=#QsNAf|VX~bBX(EDce-AOGN0i_w}{Y>{@A zR)HmiS}0NqYTYa~YT06w)>UlQGHzM*yJ1F(Vn{dbhDViPj|FeH?)ojelw9sS+5q_D6>*xq@VvYseqR$Pu~x|LNTMbyzwnboOA3c8u^P$PLrr#+PA+bOeB-bjyb<$aMHI5C!W zMSYQO-7LiXlyyb@kt~%0$gIv_qzlzXDYFud5%jod5e(^o_T_{oHNi$*@$ir-Xg zYq?m>g9j45R$sr9@D50We z=A)EZDK&(P*SR(|gzMPFl!vmOC}mc{8^YD;oG*k++{Jd5b%uQ*-0aS``zh-T`$JhO z1&~>(V5kd)FlAP(F+^)ahQFlC*b4k5dBv)nd9m;ty9N?VYeBBppCZGAz25uo=tG zvJpWuvu!iOW_$NchTzL-SKI8c+15S7mN0YCxm0dpWqj`}bVT+;6@_L7`1FdFjz%JL z!$zbN=8bm?PfsMN_Pm2w2^oOZ#mipZ>QSg1<7E$)ZwCQpq}a&n@aR^?i(0L2URLQ1 zU_jahGFa+!!dS>mLm>(O6u?RcBePur8Sep>K7b_Gz(>tkQ)Cu;#mf?2yk;urd0E7} z5^ssj2wQEun*h3^Ga@Zve8pjH3iN8tj5LSMXpe(fU9%%iVKZq@hM^bRXGa=o3i*uT z+s@obFl;8^1ZcLf%H~D_7=(iFw&}SMv#qB|=hKKn3foX#m=iJE zds=k@ONU(boM?M^7^Ye8oy38ES2vyVPh+ z<(q5_22UiZNX|iE`vBm;$0WQk#R`?%c-aF#5GBBD?P+8Zo+7*qYPB-FtiqDFF(APK zrDzmVu;6o+!ag$<1tL-Z6oBmbY*#>r04zFy7}t=C=s@eNrB<4kCHRwODtGg;2p7=a z(mG?Q73Tc}kPgjgZCPq1I3SS3%+}_mW~kditn}>Grln@AhXY2jCo;RW5p$T~Q--&D zZfkHUmY`kEhUT^gF!XLd$5zKYJUJ`DpMVvf-|EFo_VD5Y6s%7Ev=+9iOR?abf`U$T z@+Z3pD*_x|SllWtg>_(aw76AVYPHW{^paL%X(-3(rL9(yFPSRb5R+-io>``LO% zTTjt}txmDX^%g<8;1m`@zFRKxmKL_+kCbhCL8}?SvtKKow@uG&HN)AaBA-SS;svKLr`3#PTZ;S%%t2RX7VSrl zT`^s3SFkIlT*c0~KKEdkOl2qcVwX&1C--T-Vl|)Z&WP!1raPCP$GAR(_^IU5v1mwc zt%Rbn&Yrmp_ank;HJ^-PZ&3|JQ-#?K4Is`cHV7@P^oVQ(3n>$si9u&)+K5r?#Ja&S zX4rt44P%TnEb5S7GxBYr{ZL1~nE^hnQcQ+JSW8AIjfo&3AGRJ5k)+@#Y=m|pn8XY( zdo{d?RBq>G54JaL1el?GBh&C`R@#eN;jUO&#RjFF0Z9%hno&$+V}fapISB2lrU0}( zne7V5r~?}n0CBD%Yv@2|R;$&?%M!Lp%~a0uGPV%;juyOnR)qHxKxcSHs0Hhm1K3jJ z+h>NFu_SU1Vs*|AHDOugIbdk{=9`E81{zV7JBcc~Uy66-ZLOv(s z&_tYtA^ZcNO)m)H?N)JuA_fX6gwf=M`QY?;ajqiH{E(IQ+4MXx3Vw|bdS1xN1~ooA z`-SklZF+9VjC40?d>TPB1Wx`PVsd>8h;DL z*8$b?@hSfFN%!MZ{OObK$ETd*hK00-;t8uUOqOJSase4?xk5Aog;r}R&`8zrAQK`FJjk_%T zA8?uON2Ts`H9k7E+iv3y>*G?lyBfD!yX;y%KDEu&oko2U`r^NK25>>czLTx6Ya8~R z>^h8Yh2uhyE2px%+w3-ujhP!(ld=bb2u@*x#)j1lCH3hjzYvbV7jC7ZD?1%^uB_K& zCCKs`thySc)lSFiQQ58+!*bh_T}g&LLKo5%8^etml4F=^>((%H!~P0uTwQ@XTDBlS zlB6EhKx4!H45dr;qAO}`5K#f|r|_YuWXmcm>Sblt1*+Got3h^Kt95IQ^g*B_iF-SA zPZJ|GHmq60YgOr%55K*MIT3A?-TiqVs;PMD0$aHQd~I5NGh7Tdu-iWEdL{ zVk%Bgoepu_=_-b6cN8t#P0$jM>u~8H!R>1e$N#juUI76g+!23Ds%u$TWeqacgIHi2 zKLsV}K*=7Vv9g?-&f{|{udWVj-Q5loovUk2SUtINppdJv&5Z|hJ|C8`ywoG+@G*gU zgl55(pw34j?Qaej+m#Ox))*^Fd8tQs zRn3i`77rF|U1PYAm!~s%pTz@={Rayo*pjPjZ2UBoIe+|s)%}R`ng2imdN#)X6fs;v zbr-qm z#vsffuI!>JTaGaD47l}Nhp9}ihMDW#dw;k#Z3_bA7Y*qaZXL+%4>=nbG}hDILu!aF ze>$SLZ4Q)Qhh%pQovyoW;f|r(DGEwL-B%GVsD{dr<(*|Hz0A0=F1xY<$=WDc2Y&x? zI?8)iL(TQfl<=;x$SAA|WBqD+x7a|KFLhC7c-WlPnj>e(XBUHZJHcHfHOSnl-8FKC zymR%a?6$_-DU&tV@6Uy4E~de%-JamKXRP1f6<3xsmr5@crbx7Lnvfb^UB71k{cFv# zZbRH$zlP*xxJNdD{MF>JBaMY+!^ZkGOq@>&^QNp(j*~B^X{q7pOr1RQvvwz?H3E7= z@+$Y9E-t#gWM0+AWw#!Aio6W(;B=q)TU%bKlV>=+U!(ANirX-r6o2hFTsLhsSaqeD z<7JhQBdk@=oGio1;ni^FPwhqz6c2FvX*=2)qo%=Jr`^JVOogU`rf52+ro-H-(>io& z9A|8Bw+9Lb@Nuve?hX#lpr(N5R%1wYt&1xy3eiTP3`UI&CO16VI5wirVT!sdOi$kwKXidIr5k^z;C74qHhKi&{^p9Zn3L<+=$PAfb^ql$*=l!4<_2bp`E(dVc{mDrDwB^& zTw|;r$cu0kHMX&VJQU}TuUXXxN6`a)$<=oR=hdd4b{4m4NkAW64ab13T7>Syl!b$MVAm_gd)i&fsnAYPxL_WO64iN-sDc2(zKKtYcXz z6jx1_mLBi&RFqp`ip5ovrEMh{EaiPDcSto^^H{bA4%<8EUR`H^?$S%G6-ho;YSn8EmXu z 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 z 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

pfdc$YG}x+4%AA!*C( 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 Sv=2=Cz_btiPkms*^U$9xsW!;~ delta 48023 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 Date: Thu, 25 Sep 2014 12:35:13 -0700 Subject: [PATCH 736/847] Update application name in docs and strings. --- README.md | 11 +++++++---- docs/Building.txt | 10 +++++----- docs/releaseChecklist.md | 6 +++--- examples/intents/res/values/strings.xml | 2 +- examples/widget/README | 2 +- res/values-de/strings.xml | 16 ++++++++-------- res/values-ja/strings.xml | 2 +- res/values-ka/strings.xml | 6 +++--- res/values-nl/strings.xml | 2 +- res/values-pl/strings.xml | 8 ++++---- res/values/strings.xml | 8 ++++---- tools/build-release | 2 +- tools/mergeTrans.sh | 2 +- 13 files changed, 40 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 84758a5a4..8618d532a 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,15 @@ -#Android Terminal Emulator +#Terminal Emulator for Android -Android Terminal Emulator is a terminal emulator for communicating with the built-in Android shell. +Terminal Emulator for Android is a terminal emulator for communicating with the built-in Android shell. It emulates a reasonably large subset of Digital Equipment Corporation VT-100 terminal codes, so that programs like "vi", "Emacs" and "NetHack" will display properly. +This application was previously named "Android Terminal Emulator". Same great +application, just with a new human-readable name. + This code is based on the "Term" application which is included in the Android Open Source Project. (Which I also wrote. :-) ) -[Download the Android Terminal Emulator from Google Play](https://play.google.com/store/apps/details?id=jackpal.androidterm) +[Download the Terminal Emulator for Android from Google Play](https://play.google.com/store/apps/details?id=jackpal.androidterm) If you are unable to use the Play Store, you can also [download from GitHub](http://jackpal.github.com/Android-Terminal-Emulator/downloads/Term.apk) @@ -20,6 +23,6 @@ Please see the [Recent Updates](http://github.com/jackpal/Android-Terminal-Emulator/wiki/Recent-Updates) page for recent updates. -Notice: Android Terminal Emulator development is complete. I am still accepting new language translations and bug fixes, but I am not accepting new features. See [Wrapping up Development on Android Terminal Emulator] +Notice: Terminal Emulator for Android development is complete. I am still accepting new language translations and bug fixes, but I am not accepting new features. See [Wrapping up Development on Terminal Emulator for Android] (https://github.com/jackpal/Android-Terminal-Emulator/wiki/Wrapping-up-Development-on-Android-Terminal-Emulator) for details of the wrapping-up process. diff --git a/docs/Building.txt b/docs/Building.txt index ae984b6d2..1d9518e33 100644 --- a/docs/Building.txt +++ b/docs/Building.txt @@ -1,7 +1,7 @@ -Obtain the Software Needed to Build Android Terminal Emulator +Obtain the Software Needed to Build Terminal Emulator for Android ------------------------------------------------------------- -To keep from typing "Android Terminal Emulator" over and over again, this +To keep from typing "Terminal Emulator for Android" over and over again, this document will use the abbreviation "ATE" to stand for "Android Terminal Emulator". @@ -17,7 +17,7 @@ http://developer.android.com/sdk Note 1: Recent (2013+) versions of the Android ADT SDK Bundle include an Android-specific version of Eclipse. -Note 2: Android Terminal Emulator can not currently be built using the new +Note 2: Terminal Emulator for Android can not currently be built using the new Android Studio IDE. Android Studio does not currently support projects that use the NDK. @@ -63,7 +63,7 @@ There are three steps to building ATE with Eclipse: Build the project from the command line using "ant": You need to build the project from the commmand line in order to generate the -shared libraries and Java JAR files used by Android Terminal Emulator. +shared libraries and Java JAR files used by Terminal Emulator for Android. Use the instructions in "Building ATE with ant", above. @@ -102,7 +102,7 @@ Build the Java apk: Command Line Build Instructions ------------------------------- -You can build Android Terminal Emulator from the command line, instead of from +You can build Terminal Emulator for Android from the command line, instead of from Eclipse. This is handy because it can be scripted. 1) Install the "ant" build system. diff --git a/docs/releaseChecklist.md b/docs/releaseChecklist.md index 9f2ce3a9e..e16a964b8 100644 --- a/docs/releaseChecklist.md +++ b/docs/releaseChecklist.md @@ -1,4 +1,4 @@ -## Android Terminal Emulator Release Checklist +## Terminal Emulator for Android Release Checklist # Test on 1.5 @@ -45,7 +45,7 @@ https://play.google.com/apps/publish The Android Developer Console Publishing UI is error prone: -1) Click on the "Android Terminal Emulator" link. +1) Click on the "Terminal Emulator for Android" link. 2) Click on the APK files tab @@ -70,7 +70,7 @@ https://play.google.com/store/apps/details?id=jackpal.androidterm (Note, it can take several hours for the app to appear in the store.) -# Update the Android Terminal Emulator Wiki +# Update the Terminal Emulator for Android Wiki https://github.com/jackpal/Android-Terminal-Emulator/wiki/Recent-Updates diff --git a/examples/intents/res/values/strings.xml b/examples/intents/res/values/strings.xml index 6c738c057..f94ff4645 100644 --- a/examples/intents/res/values/strings.xml +++ b/examples/intents/res/values/strings.xml @@ -6,7 +6,7 @@ Run Script Run Script and Save Window Run Script in Saved Window - This sample shows how to send intents to Android Terminal Emulator. + This sample shows how to send intents to Terminal Emulator for Android. echo \'Hello, world!\' diff --git a/examples/widget/README b/examples/widget/README index 94b2745e7..4c14e0824 100644 --- a/examples/widget/README +++ b/examples/widget/README @@ -1,6 +1,6 @@ This example requires a few manual build steps. -# Build the regular Android Terminal Emulator +# Build the regular Terminal Emulator for Android cd path-AndroidTerminalEmulator-root-directory tools/update.sh diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml index f6134a5c8..3105db544 100644 --- a/res/values-de/strings.xml +++ b/res/values-de/strings.xml @@ -26,22 +26,22 @@ Email schreiben Spezialtasten Tastatur an/aus - + Der Status dieses Terminalfensters wurde zurückgesetzt. Bildschirm immer anlassen Bildschirm ist immer an WLAN immer anlassen WLAN ist immer an - + Text bearbeiten Text auswählen Alles kopieren - Einfügen - + Einfügen + Terminalsitzung wird ausgeführt - Fenster %1$d + Fenster %1$d Terminalsitzung beendet @@ -54,7 +54,7 @@ Aktionsleiste Wählen sie das Verhalten der Aktionsleiste (ab Android 3.0). Verhalten der Aktionsleiste - + Aussehen des Cursor Das Aussehen des Cursors auswählen. Aussehen des Cursor @@ -123,5 +123,5 @@ Dieses Fenster schließen? Beliebige Scripte im Terminal Emulator ausführen - Erlaubt Anwendungen, neue Fenster im Android Terminal Emulator zu öffnen und in diesen Befehle auszuführen. Dies schließt alle Berechtigungen von Android Terminal Emulator ein, inklusive Internetzugang und Schreib-/Leserechte auf der SD-Karte. - \ No newline at end of file + Erlaubt Anwendungen, neue Fenster im Terminal Emulator for Android zu öffnen und in diesen Befehle auszuführen. Dies schließt alle Berechtigungen von Terminal Emulator for Android ein, inklusive Internetzugang und Schreib-/Leserechte auf der SD-Karte. + diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml index 502fa236c..671cce0bb 100644 --- a/res/values-ja/strings.xml +++ b/res/values-ja/strings.xml @@ -141,7 +141,7 @@ 端末エミュレータのコマンドを上書きする アプリケーションに対して,端末エミュレータが持つ既存のコマンドを,アプリケーション独自のバージョンに置き換える(PATHの先頭にディレクトリを追加する)ことを許可する. - Transcript from Android Terminal Emulator + Transcript from Terminal Emulator for Android メール転写の選択: 転写を送信するEメール用アクティビティが選択できませんでした. diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml index 06629d05c..f03e1226b 100644 --- a/res/values-ka/strings.xml +++ b/res/values-ka/strings.xml @@ -135,9 +135,9 @@ გნებავთ ფანჯრის დახურვა? ტერმინალში სასურველი ბრძანებების გაშვება - Allows application to open new windows in Android Terminal Emulator and run commands in those windows with all of Android Terminal Emulator\'s permissions, including access to the Internet and your SD Card. + Allows application to open new windows in Terminal Emulator for Android and run commands in those windows with all of Terminal Emulator for Android\'s permissions, including access to the Internet and your SD Card. Terminal Emulator-სათვის ბრძანებების დამატება - Allows application to provide additional commands (add directories to the PATH) for Android Terminal Emulator. + Allows application to provide additional commands (add directories to the PATH) for Terminal Emulator for Android. Terminal Emulator-ის ბრძანებების უგულებელყოფა - Allows application to override existing commands with its own versions (prepend directories to the PATH) in Android Terminal Emulator. + Allows application to override existing commands with its own versions (prepend directories to the PATH) in Terminal Emulator for Android. diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml index 38d2cc41a..d342b71af 100644 --- a/res/values-nl/strings.xml +++ b/res/values-nl/strings.xml @@ -131,7 +131,7 @@ Dit venster sluiten? - Gegevens uit Android Terminal Emulator + Gegevens uit Terminal Emulator for Android Gegevens e-mailen via: Kon geen e-mail activity kiezen om gegevens te sturen diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml index 25b3b43c3..ca8ee3cbe 100644 --- a/res/values-pl/strings.xml +++ b/res/values-pl/strings.xml @@ -135,13 +135,13 @@ Zamknąć to okno? Uruchamiaj komendy w Terminal Emulator - Pozwalaj na otwieranie okien w Android Terminal Emulator i wykonywanie komend w tych oknach z wszystkimi prawami Android Terminal Emulator, wliczając dostęp do internetu i karty SD. + Pozwalaj na otwieranie okien w Terminal Emulator for Android i wykonywanie komend w tych oknach z wszystkimi prawami Terminal Emulator for Android, wliczając dostęp do internetu i karty SD. Dodawanie komend w Terminal Emulator - Pozwól aplikacjom na dodawanie dodatkowych komend (odawanie katalogów do PATH) w Android Terminal Emulator. + Pozwól aplikacjom na dodawanie dodatkowych komend (odawanie katalogów do PATH) w Terminal Emulator for Android. Nadpisuj komendy Terminal Emulator - Pozwól aplikacjom na nadpisywanie istniejących komend ich własnymi wersjami (dodawanie katalogów na początku PATH) w Android Terminal Emulator. + Pozwól aplikacjom na nadpisywanie istniejących komend ich własnymi wersjami (dodawanie katalogów na początku PATH) w Terminal Emulator for Android. - Transkrypt z Android Terminal Emulator + Transkrypt z Terminal Emulator for Android Wyślij transkrypt używając: Nie można wybrać aktywności e-mail żeby wysłać transkrypt. diff --git a/res/values/strings.xml b/res/values/strings.xml index 3b4db5cc5..62c3d0333 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -142,13 +142,13 @@ Close this window? Run arbitrary commands in Terminal Emulator - Allows application to open new windows in Android Terminal Emulator and run commands in those windows with all of Android Terminal Emulator\'s permissions, including access to the Internet and your SD Card. + Allows application to open new windows in Terminal Emulator for Android and run commands in those windows with all of Terminal Emulator for Android\'s permissions, including access to the Internet and your SD Card. Add commands to Terminal Emulator - Allows application to provide additional commands (add directories to the PATH) for Android Terminal Emulator. + Allows application to provide additional commands (add directories to the PATH) for Terminal Emulator for Android. Override commands in Terminal Emulator - Allows application to override existing commands with its own versions (prepend directories to the PATH) in Android Terminal Emulator. + Allows application to override existing commands with its own versions (prepend directories to the PATH) in Terminal Emulator for Android. - Transcript from Android Terminal Emulator + Transcript from Terminal Emulator for Android Mail transcript using: Could not choose an email activity to send transcript. diff --git a/tools/build-release b/tools/build-release index 586ee466f..549e9c65a 100755 --- a/tools/build-release +++ b/tools/build-release @@ -2,7 +2,7 @@ # command line build script for building release version # This will only work on my (Jack Palevich) computer, because it # requires the private key store used to sign the release version -# of Android Terminal Emulator. +# of Terminal Emulator for Android. set -e export ANDROID_TERMINAL_EMULATOR_KEYSTORE="$HOME/Documents/workspace/keystore/jackpal.keystore" diff --git a/tools/mergeTrans.sh b/tools/mergeTrans.sh index 33b65a07e..0cf11c9ca 100755 --- a/tools/mergeTrans.sh +++ b/tools/mergeTrans.sh @@ -1,6 +1,6 @@ #!/bin/bash # A script to merge selected translations from the CyanogenMOD version of -# Android Terminal Emulator to the upstream version. +# Terminal Emulator for Android to the upstream version. set -e From 876f6cae9941bb6cbeb8e1f128f417ba2bdc99b8 Mon Sep 17 00:00:00 2001 From: pyler Date: Sat, 4 Oct 2014 12:32:59 +0200 Subject: [PATCH 737/847] Updated Slovak translation --- res/values-sk/strings.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml index 1aa6aff65..854910a80 100644 --- a/res/values-sk/strings.xml +++ b/res/values-sk/strings.xml @@ -138,15 +138,15 @@ Zavrieť toto okno? Spustiť ľubovoľné príkazy - Umožniť aplikáciám otvoriť nové okno v Emulátori terminálu Android a spustiť príkazy v tomto okne so všetkými povoleniami terminálu a to vrátane prístupu na Internet a ku karte SD. + Umožniť aplikáciám otvoriť nové okno v Emulátori terminálu pre Android a spustiť príkazy v tomto okne so všetkými povoleniami terminálu a to vrátane prístupu na Internet a ku karte SD. Pridať príkazy do Emulátora terminálu Android - Umožňuje aplikáciám poskytnúť ďalšie príkazy (pridať priečinky do PATH) v Emulátori terminálu Android. + Umožňuje aplikáciám poskytnúť ďalšie príkazy (pridať priečinky do PATH) v Emulátori terminálu pre Android. Prepísať príkazy v Emulátori terminálu Android - Umožňuje aplikáciám prepísať existujúce príkazy so svojimi vlastnými verziami (podradiť priečinky v PATH) v Emulátori terminálu Android. + Umožňuje aplikáciám prepísať existujúce príkazy so svojimi vlastnými verziami (podradiť priečinky v PATH) v Emulátori terminálu pre Android. - Prepis z Emulátora terminálu Android + Prepis z Emulátora terminálu pre Android Poslať prepis prostredníctvom - Nemožno vybrať e-mailovú aktivitu na poslanie prepisu. + Nie je možné vybrať e-mailovú aktivitu na poslanie prepisu. Posielať ESC pomocou klávesu Alt Kláves Alt posiela ESC. From 6f0fe937234cba9122f1de432645960da2a4d084 Mon Sep 17 00:00:00 2001 From: Jack Palevich Date: Sun, 14 Dec 2014 08:58:08 -0800 Subject: [PATCH 738/847] Convert to Android Studio 1.0 Builds and runs on Android 5.0 (Lollipop) Need to update documentation, bring over examples, test, etc. --- .gitignore | 19 +- .idea/.name | 1 + .idea/compiler.xml | 23 + .idea/copyright/profiles_settings.xml | 3 + .idea/encodings.xml | 5 + .idea/gradle.xml | 20 + .idea/misc.xml | 7 + .idea/modules.xml | 11 + .idea/scopes/scope_settings.xml | 5 + .idea/vcs.xml | 7 + Android-Terminal-Emulator.iml | 21 + Android.mk | 40 - ant.properties | 19 - build.gradle | 15 + emulatorview/.gitignore | 1 + emulatorview/build.gradle | 18 + emulatorview/emulatorview.iml | 89 ++ .../src/main}/AndroidManifest.xml | 0 .../emulatorview/BaseTextRenderer.java | 0 .../emulatorview/Bitmap4x8FontRenderer.java | 0 .../androidterm/emulatorview/ByteQueue.java | 0 .../androidterm/emulatorview/ColorScheme.java | 0 .../emulatorview/EmulatorDebug.java | 0 .../emulatorview/EmulatorView.java | 0 .../emulatorview/GrowableIntArray.java | 0 .../emulatorview/PaintRenderer.java | 0 .../androidterm/emulatorview/Screen.java | 0 .../androidterm/emulatorview/StyleRow.java | 0 .../emulatorview/TermKeyListener.java | 0 .../androidterm/emulatorview/TermSession.java | 0 .../emulatorview/TerminalEmulator.java | 0 .../emulatorview/TextRenderer.java | 0 .../androidterm/emulatorview/TextStyle.java | 0 .../emulatorview/TranscriptScreen.java | 0 .../emulatorview/UnicodeTranscript.java | 0 .../emulatorview/UpdateCallback.java | 0 .../compat/AndroidCharacterCompat.java | 0 .../emulatorview/compat/AndroidCompat.java | 0 .../compat/ClipboardManagerCompat.java | 0 .../compat/ClipboardManagerCompatFactory.java | 0 .../compat/ClipboardManagerCompatV1.java | 0 .../compat/ClipboardManagerCompatV11.java | 0 .../compat/KeyCharacterMapCompat.java | 0 .../emulatorview/compat/KeycodeConstants.java | 0 .../emulatorview/compat/Patterns.java | 0 .../androidterm/emulatorview/package.html | 0 .../res/drawable-nodpi/atari_small_nodpi.png | Bin .../src/main}/res/drawable/atari_small.png | Bin examples/intents/AndroidManifest.xml | 17 - examples/intents/ant.properties | 17 - examples/intents/proguard-project.txt | 20 - examples/intents/project.properties | 14 - examples/intents/res/layout/main.xml | 42 - examples/intents/res/values-ja/strings.xml | 13 - examples/intents/res/values-ko/strings.xml | 10 - examples/intents/res/values/strings.xml | 13 - .../sample/intents/IntentSampleActivity.java | 84 - examples/pathbroadcasts/AndroidManifest.xml | 22 - examples/pathbroadcasts/ant.properties | 17 - examples/pathbroadcasts/assets/hello | 4 - examples/pathbroadcasts/assets/ls | 4 - examples/pathbroadcasts/proguard-project.txt | 20 - examples/pathbroadcasts/project.properties | 14 - .../pathbroadcasts/res/values/strings.xml | 4 - .../sample/pathbroadcasts/PathReceiver.java | 166 -- examples/widget/AndroidManifest.xml | 21 - examples/widget/Makefile | 47 - examples/widget/README | 15 - examples/widget/ant.properties | 17 - examples/widget/assets-src/execpty.c | 235 --- examples/widget/custom_rules.xml | 13 - examples/widget/proguard-project.txt | 20 - examples/widget/project.properties | 15 - .../widget/res/layout/launch_activity.xml | 37 - examples/widget/res/layout/term_activity.xml | 39 - examples/widget/res/values-ko/strings.xml | 12 - examples/widget/res/values/strings.xml | 12 - .../sample/telnet/LaunchActivity.java | 143 -- .../sample/telnet/TelnetSession.java | 568 ------- .../sample/telnet/TermActivity.java | 281 ---- gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 49896 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 164 ++ gradlew.bat | 90 ++ libraries/emulatorview/ant.properties | 17 - .../emulatorview/docs/allclasses-frame.html | 39 - .../emulatorview/docs/allclasses-noframe.html | 39 - .../emulatorview/docs/constant-values.html | 142 -- .../emulatorview/docs/deprecated-list.html | 142 -- libraries/emulatorview/docs/generate-docs.sh | 16 - libraries/emulatorview/docs/help-doc.html | 209 --- libraries/emulatorview/docs/index-all.html | 452 ------ libraries/emulatorview/docs/index.html | 36 - .../androidterm/emulatorview/ColorScheme.html | 369 ----- .../emulatorview/EmulatorView.html | 1406 ----------------- .../TermSession.FinishCallback.html | 213 --- .../androidterm/emulatorview/TermSession.html | 808 ---------- .../emulatorview/UpdateCallback.html | 208 --- .../emulatorview/package-frame.html | 49 - .../emulatorview/package-summary.html | 201 --- .../emulatorview/package-tree.html | 155 -- .../emulatorview/docs/overview-tree.html | 157 -- libraries/emulatorview/docs/package-list | 1 - .../emulatorview/docs/resources/inherit.gif | Bin 57 -> 0 bytes libraries/emulatorview/docs/stylesheet.css | 29 - libraries/emulatorview/proguard-project.txt | 20 - libraries/emulatorview/project.properties | 15 - proguard-project.txt | 20 - proguard.cfg | 40 - project.properties | 12 - settings.gradle | 2 + term/.gitignore | 1 + term/build.gradle | 28 + lint.xml => term/lint.xml | 2 +- .../src/main/AndroidManifest.xml | 0 .../main/java}/jackpal/androidterm/Exec.java | 0 .../jackpal/androidterm/RemoteInterface.java | 0 .../jackpal/androidterm/ShellTermSession.java | 0 .../main/java}/jackpal/androidterm/Term.java | 0 .../java}/jackpal/androidterm/TermDebug.java | 0 .../jackpal/androidterm/TermPreferences.java | 0 .../jackpal/androidterm/TermService.java | 0 .../java}/jackpal/androidterm/TermView.java | 0 .../jackpal/androidterm/TermViewFlipper.java | 0 .../java}/jackpal/androidterm/WindowList.java | 0 .../androidterm/WindowListAdapter.java | 0 .../androidterm/compat/ActionBarCompat.java | 0 .../androidterm/compat/ActivityCompat.java | 0 .../androidterm/compat/AlertDialogCompat.java | 0 .../androidterm/compat/AndroidCompat.java | 0 .../androidterm/compat/FileCompat.java | 0 .../androidterm/compat/MenuItemCompat.java | 0 .../compat/ServiceForegroundCompat.java | 0 .../androidterm/shortcuts/AddShortcut.java | 0 .../androidterm/shortcuts/ColorValue.java | 0 .../androidterm/shortcuts/FSNavigator.java | 0 .../androidterm/shortcuts/TextIcon.java | 0 .../jackpal/androidterm/util/SessionList.java | 0 .../androidterm/util/TermSettings.java | 0 {jni => term/src/main/jni}/Android.mk | 0 {jni => term/src/main/jni}/Application.mk | 0 {jni => term/src/main/jni}/common.cpp | 0 {jni => term/src/main/jni}/common.h | 0 {jni => term/src/main/jni}/fileCompat.cpp | 0 {jni => term/src/main/jni}/fileCompat.h | 0 {jni => term/src/main/jni}/termExec.cpp | 0 {jni => term/src/main/jni}/termExec.h | 0 .../res}/drawable-hdpi-v11/ic_menu_add.png | Bin .../res}/drawable-hdpi-v11/ic_menu_back.png | Bin .../ic_menu_close_clear_cancel.png | Bin .../drawable-hdpi-v11/ic_menu_forward.png | Bin .../drawable-hdpi-v11/ic_menu_preferences.png | Bin .../ic_stat_service_notification_icon.png | Bin .../ic_stat_service_notification_icon.png | Bin .../res}/drawable-hdpi/btn_close_window.png | Bin .../main/res}/drawable-hdpi/ic_launcher.png | Bin .../main/res}/drawable-hdpi/ic_menu_add.png | Bin .../main/res}/drawable-hdpi/ic_menu_back.png | Bin .../ic_menu_close_clear_cancel.png | Bin .../res}/drawable-hdpi/ic_menu_forward.png | Bin .../drawable-hdpi/ic_menu_preferences.png | Bin .../res}/drawable-hdpi/ic_menu_windows.png | Bin .../ic_stat_service_notification_icon.png | Bin .../res}/drawable-ldpi-v11/ic_menu_add.png | Bin .../res}/drawable-ldpi-v11/ic_menu_back.png | Bin .../ic_menu_close_clear_cancel.png | Bin .../drawable-ldpi-v11/ic_menu_forward.png | Bin .../drawable-ldpi-v11/ic_menu_preferences.png | Bin .../ic_stat_service_notification_icon.png | Bin .../ic_stat_service_notification_icon.png | Bin .../main/res}/drawable-ldpi/ic_launcher.png | Bin .../main/res}/drawable-ldpi/ic_menu_add.png | Bin .../ic_menu_close_clear_cancel.png | Bin .../drawable-ldpi/ic_menu_preferences.png | Bin .../ic_stat_service_notification_icon.png | Bin .../res}/drawable-mdpi-v11/ic_menu_add.png | Bin .../res}/drawable-mdpi-v11/ic_menu_back.png | Bin .../ic_menu_close_clear_cancel.png | Bin .../drawable-mdpi-v11/ic_menu_forward.png | Bin .../drawable-mdpi-v11/ic_menu_preferences.png | Bin .../ic_stat_service_notification_icon.png | Bin .../ic_stat_service_notification_icon.png | Bin .../res}/drawable-mdpi/btn_close_window.png | Bin .../main/res}/drawable-mdpi/ic_launcher.png | Bin .../main/res}/drawable-mdpi/ic_menu_add.png | Bin .../main/res}/drawable-mdpi/ic_menu_back.png | Bin .../ic_menu_close_clear_cancel.png | Bin .../res}/drawable-mdpi/ic_menu_forward.png | Bin .../drawable-mdpi/ic_menu_preferences.png | Bin .../res}/drawable-mdpi/ic_menu_windows.png | Bin .../ic_stat_service_notification_icon.png | Bin .../res}/drawable-xhdpi-v11/ic_menu_add.png | Bin .../res}/drawable-xhdpi-v11/ic_menu_back.png | Bin .../ic_menu_close_clear_cancel.png | Bin .../drawable-xhdpi-v11/ic_menu_forward.png | Bin .../ic_menu_preferences.png | Bin .../ic_stat_service_notification_icon.png | Bin .../ic_stat_service_notification_icon.png | Bin .../main/res}/drawable-xhdpi/ic_launcher.png | Bin .../ic_stat_service_notification_icon.png | Bin .../main/res}/drawable-xxhdpi/ic_launcher.png | Bin .../res}/drawable-xxxhdpi/ic_launcher.png | Bin .../main/res}/drawable/btn_close_window.png | Bin .../main/res}/drawable/close_background.xml | 0 .../src/main/res}/drawable/ic_folder.png | Bin .../src/main/res}/drawable/ic_folderup.png | Bin .../src/main/res}/drawable/ic_launcher.png | Bin .../src/main/res}/drawable/ic_menu_add.png | Bin .../src/main/res}/drawable/ic_menu_back.png | Bin .../drawable/ic_menu_close_clear_cancel.png | Bin .../main/res}/drawable/ic_menu_forward.png | Bin .../res}/drawable/ic_menu_preferences.png | Bin .../main/res}/drawable/ic_menu_windows.png | Bin .../ic_stat_service_notification_icon.png | Bin .../src/main/res}/layout/term_activity.xml | 0 .../src/main/res}/layout/window_list_item.xml | 0 .../res}/layout/window_list_new_window.xml | 0 {res => term/src/main/res}/menu/main.xml | 0 .../src/main/res}/values-cs/arrays.xml | 0 .../src/main/res}/values-cs/strings.xml | 0 .../src/main/res}/values-de/arrays.xml | 0 .../src/main/res}/values-de/strings.xml | 0 .../src/main/res}/values-es/arrays.xml | 0 .../src/main/res}/values-es/strings.xml | 0 .../src/main/res}/values-eu/arrays.xml | 0 .../src/main/res}/values-eu/strings.xml | 0 .../src/main/res}/values-fr/arrays.xml | 0 .../src/main/res}/values-fr/strings.xml | 0 .../src/main/res}/values-hu/arrays.xml | 0 .../src/main/res}/values-hu/strings.xml | 0 .../src/main/res}/values-it/arrays.xml | 0 .../src/main/res}/values-it/strings.xml | 0 .../src/main/res}/values-iw/arrays.xml | 0 .../src/main/res}/values-iw/strings.xml | 0 .../src/main/res}/values-ja/arrays.xml | 0 .../src/main/res}/values-ja/strings.xml | 0 .../src/main/res}/values-ka/arrays.xml | 0 .../src/main/res}/values-ka/strings.xml | 0 .../src/main/res}/values-ko/arrays.xml | 0 .../src/main/res}/values-ko/strings.xml | 0 .../src/main/res}/values-nb/arrays.xml | 0 .../src/main/res}/values-nb/strings.xml | 0 .../src/main/res}/values-nl/arrays.xml | 0 .../src/main/res}/values-nl/strings.xml | 0 .../src/main/res}/values-pl/arrays.xml | 0 .../src/main/res}/values-pl/strings.xml | 0 .../src/main/res}/values-pt-rPT/arrays.xml | 0 .../src/main/res}/values-pt-rPT/strings.xml | 0 .../src/main/res}/values-pt/arrays.xml | 0 .../src/main/res}/values-pt/strings.xml | 0 .../src/main/res}/values-ro/arrays.xml | 0 .../src/main/res}/values-ro/strings.xml | 0 .../src/main/res}/values-ru/arrays.xml | 0 .../src/main/res}/values-ru/strings.xml | 0 .../src/main/res}/values-sk/arrays.xml | 0 .../src/main/res}/values-sk/strings.xml | 0 .../src/main/res}/values-sr/arrays.xml | 0 .../src/main/res}/values-sr/strings.xml | 0 .../src/main/res}/values-sv/arrays.xml | 2 +- .../src/main/res}/values-sv/strings.xml | 2 +- .../src/main/res}/values-tr/arrays.xml | 0 .../src/main/res}/values-tr/strings.xml | 0 .../src/main/res}/values-uk/arrays.xml | 0 .../src/main/res}/values-uk/strings.xml | 0 .../src/main/res}/values-zh-rCN/arrays.xml | 0 .../src/main/res}/values-zh-rCN/strings.xml | 0 .../src/main/res}/values-zh-rTW/arrays.xml | 0 .../src/main/res}/values-zh-rTW/strings.xml | 0 {res => term/src/main/res}/values/arrays.xml | 0 .../src/main/res}/values/arraysNoLocalize.xml | 0 {res => term/src/main/res}/values/attrs.xml | 0 .../src/main/res}/values/defaults.xml | 0 {res => term/src/main/res}/values/id.xml | 0 {res => term/src/main/res}/values/strings.xml | 0 {res => term/src/main/res}/values/styles.xml | 0 .../src/main/res}/xml/preferences.xml | 0 term/term.iml | 89 ++ tools/build-debug | 37 - tools/build-release | 47 - tools/install-launcher-icons.sh | 41 - tools/install_service_icons.sh | 36 - tools/mergeTrans.sh | 37 - tools/printFnEscapeSequences | 18 - tools/pushAndRun | 18 - tools/pushAndRun-debug | 18 - tools/runTests | 8 - tools/stuffStrings.go | 72 - tools/update.sh | 44 - 288 files changed, 615 insertions(+), 7202 deletions(-) create mode 100644 .idea/.name create mode 100644 .idea/compiler.xml create mode 100644 .idea/copyright/profiles_settings.xml create mode 100644 .idea/encodings.xml create mode 100644 .idea/gradle.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/scopes/scope_settings.xml create mode 100644 .idea/vcs.xml create mode 100644 Android-Terminal-Emulator.iml delete mode 100644 Android.mk delete mode 100644 ant.properties create mode 100644 build.gradle create mode 100644 emulatorview/.gitignore create mode 100644 emulatorview/build.gradle create mode 100644 emulatorview/emulatorview.iml rename {libraries/emulatorview => emulatorview/src/main}/AndroidManifest.xml (100%) rename {libraries/emulatorview/src => emulatorview/src/main/java}/jackpal/androidterm/emulatorview/BaseTextRenderer.java (100%) rename {libraries/emulatorview/src => emulatorview/src/main/java}/jackpal/androidterm/emulatorview/Bitmap4x8FontRenderer.java (100%) rename {libraries/emulatorview/src => emulatorview/src/main/java}/jackpal/androidterm/emulatorview/ByteQueue.java (100%) rename {libraries/emulatorview/src => emulatorview/src/main/java}/jackpal/androidterm/emulatorview/ColorScheme.java (100%) rename {libraries/emulatorview/src => emulatorview/src/main/java}/jackpal/androidterm/emulatorview/EmulatorDebug.java (100%) rename {libraries/emulatorview/src => emulatorview/src/main/java}/jackpal/androidterm/emulatorview/EmulatorView.java (100%) rename {libraries/emulatorview/src => emulatorview/src/main/java}/jackpal/androidterm/emulatorview/GrowableIntArray.java (100%) rename {libraries/emulatorview/src => emulatorview/src/main/java}/jackpal/androidterm/emulatorview/PaintRenderer.java (100%) rename {libraries/emulatorview/src => emulatorview/src/main/java}/jackpal/androidterm/emulatorview/Screen.java (100%) rename {libraries/emulatorview/src => emulatorview/src/main/java}/jackpal/androidterm/emulatorview/StyleRow.java (100%) rename {libraries/emulatorview/src => emulatorview/src/main/java}/jackpal/androidterm/emulatorview/TermKeyListener.java (100%) rename {libraries/emulatorview/src => emulatorview/src/main/java}/jackpal/androidterm/emulatorview/TermSession.java (100%) rename {libraries/emulatorview/src => emulatorview/src/main/java}/jackpal/androidterm/emulatorview/TerminalEmulator.java (100%) rename {libraries/emulatorview/src => emulatorview/src/main/java}/jackpal/androidterm/emulatorview/TextRenderer.java (100%) rename {libraries/emulatorview/src => emulatorview/src/main/java}/jackpal/androidterm/emulatorview/TextStyle.java (100%) rename {libraries/emulatorview/src => emulatorview/src/main/java}/jackpal/androidterm/emulatorview/TranscriptScreen.java (100%) rename {libraries/emulatorview/src => emulatorview/src/main/java}/jackpal/androidterm/emulatorview/UnicodeTranscript.java (100%) rename {libraries/emulatorview/src => emulatorview/src/main/java}/jackpal/androidterm/emulatorview/UpdateCallback.java (100%) rename {libraries/emulatorview/src => emulatorview/src/main/java}/jackpal/androidterm/emulatorview/compat/AndroidCharacterCompat.java (100%) rename {libraries/emulatorview/src => emulatorview/src/main/java}/jackpal/androidterm/emulatorview/compat/AndroidCompat.java (100%) rename {libraries/emulatorview/src => emulatorview/src/main/java}/jackpal/androidterm/emulatorview/compat/ClipboardManagerCompat.java (100%) rename {libraries/emulatorview/src => emulatorview/src/main/java}/jackpal/androidterm/emulatorview/compat/ClipboardManagerCompatFactory.java (100%) rename {libraries/emulatorview/src => emulatorview/src/main/java}/jackpal/androidterm/emulatorview/compat/ClipboardManagerCompatV1.java (100%) rename {libraries/emulatorview/src => emulatorview/src/main/java}/jackpal/androidterm/emulatorview/compat/ClipboardManagerCompatV11.java (100%) rename {libraries/emulatorview/src => emulatorview/src/main/java}/jackpal/androidterm/emulatorview/compat/KeyCharacterMapCompat.java (100%) rename {libraries/emulatorview/src => emulatorview/src/main/java}/jackpal/androidterm/emulatorview/compat/KeycodeConstants.java (100%) rename {libraries/emulatorview/src => emulatorview/src/main/java}/jackpal/androidterm/emulatorview/compat/Patterns.java (100%) rename {libraries/emulatorview/src => emulatorview/src/main/java}/jackpal/androidterm/emulatorview/package.html (100%) rename {libraries/emulatorview => emulatorview/src/main}/res/drawable-nodpi/atari_small_nodpi.png (100%) rename {libraries/emulatorview => emulatorview/src/main}/res/drawable/atari_small.png (100%) delete mode 100644 examples/intents/AndroidManifest.xml delete mode 100644 examples/intents/ant.properties delete mode 100644 examples/intents/proguard-project.txt delete mode 100644 examples/intents/project.properties delete mode 100644 examples/intents/res/layout/main.xml delete mode 100644 examples/intents/res/values-ja/strings.xml delete mode 100644 examples/intents/res/values-ko/strings.xml delete mode 100644 examples/intents/res/values/strings.xml delete mode 100644 examples/intents/src/jackpal/androidterm/sample/intents/IntentSampleActivity.java delete mode 100644 examples/pathbroadcasts/AndroidManifest.xml delete mode 100644 examples/pathbroadcasts/ant.properties delete mode 100755 examples/pathbroadcasts/assets/hello delete mode 100755 examples/pathbroadcasts/assets/ls delete mode 100644 examples/pathbroadcasts/proguard-project.txt delete mode 100644 examples/pathbroadcasts/project.properties delete mode 100644 examples/pathbroadcasts/res/values/strings.xml delete mode 100644 examples/pathbroadcasts/src/jackpal/androidterm/sample/pathbroadcasts/PathReceiver.java delete mode 100644 examples/widget/AndroidManifest.xml delete mode 100644 examples/widget/Makefile delete mode 100644 examples/widget/README delete mode 100644 examples/widget/ant.properties delete mode 100644 examples/widget/assets-src/execpty.c delete mode 100644 examples/widget/custom_rules.xml delete mode 100644 examples/widget/proguard-project.txt delete mode 100644 examples/widget/project.properties delete mode 100644 examples/widget/res/layout/launch_activity.xml delete mode 100644 examples/widget/res/layout/term_activity.xml delete mode 100644 examples/widget/res/values-ko/strings.xml delete mode 100644 examples/widget/res/values/strings.xml delete mode 100644 examples/widget/src/jackpal/androidterm/sample/telnet/LaunchActivity.java delete mode 100644 examples/widget/src/jackpal/androidterm/sample/telnet/TelnetSession.java delete mode 100644 examples/widget/src/jackpal/androidterm/sample/telnet/TermActivity.java create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat delete mode 100644 libraries/emulatorview/ant.properties delete mode 100644 libraries/emulatorview/docs/allclasses-frame.html delete mode 100644 libraries/emulatorview/docs/allclasses-noframe.html delete mode 100644 libraries/emulatorview/docs/constant-values.html delete mode 100644 libraries/emulatorview/docs/deprecated-list.html delete mode 100755 libraries/emulatorview/docs/generate-docs.sh delete mode 100644 libraries/emulatorview/docs/help-doc.html delete mode 100644 libraries/emulatorview/docs/index-all.html delete mode 100644 libraries/emulatorview/docs/index.html delete mode 100644 libraries/emulatorview/docs/jackpal/androidterm/emulatorview/ColorScheme.html delete mode 100644 libraries/emulatorview/docs/jackpal/androidterm/emulatorview/EmulatorView.html delete mode 100644 libraries/emulatorview/docs/jackpal/androidterm/emulatorview/TermSession.FinishCallback.html delete mode 100644 libraries/emulatorview/docs/jackpal/androidterm/emulatorview/TermSession.html delete mode 100644 libraries/emulatorview/docs/jackpal/androidterm/emulatorview/UpdateCallback.html delete mode 100644 libraries/emulatorview/docs/jackpal/androidterm/emulatorview/package-frame.html delete mode 100644 libraries/emulatorview/docs/jackpal/androidterm/emulatorview/package-summary.html delete mode 100644 libraries/emulatorview/docs/jackpal/androidterm/emulatorview/package-tree.html delete mode 100644 libraries/emulatorview/docs/overview-tree.html delete mode 100644 libraries/emulatorview/docs/package-list delete mode 100644 libraries/emulatorview/docs/resources/inherit.gif delete mode 100644 libraries/emulatorview/docs/stylesheet.css delete mode 100644 libraries/emulatorview/proguard-project.txt delete mode 100644 libraries/emulatorview/project.properties delete mode 100644 proguard-project.txt delete mode 100644 proguard.cfg delete mode 100644 project.properties create mode 100644 settings.gradle create mode 100644 term/.gitignore create mode 100644 term/build.gradle rename lint.xml => term/lint.xml (63%) rename AndroidManifest.xml => term/src/main/AndroidManifest.xml (100%) rename {src => term/src/main/java}/jackpal/androidterm/Exec.java (100%) rename {src => term/src/main/java}/jackpal/androidterm/RemoteInterface.java (100%) rename {src => term/src/main/java}/jackpal/androidterm/ShellTermSession.java (100%) rename {src => term/src/main/java}/jackpal/androidterm/Term.java (100%) rename {src => term/src/main/java}/jackpal/androidterm/TermDebug.java (100%) rename {src => term/src/main/java}/jackpal/androidterm/TermPreferences.java (100%) rename {src => term/src/main/java}/jackpal/androidterm/TermService.java (100%) rename {src => term/src/main/java}/jackpal/androidterm/TermView.java (100%) rename {src => term/src/main/java}/jackpal/androidterm/TermViewFlipper.java (100%) rename {src => term/src/main/java}/jackpal/androidterm/WindowList.java (100%) rename {src => term/src/main/java}/jackpal/androidterm/WindowListAdapter.java (100%) rename {src => term/src/main/java}/jackpal/androidterm/compat/ActionBarCompat.java (100%) rename {src => term/src/main/java}/jackpal/androidterm/compat/ActivityCompat.java (100%) rename {src => term/src/main/java}/jackpal/androidterm/compat/AlertDialogCompat.java (100%) rename {src => term/src/main/java}/jackpal/androidterm/compat/AndroidCompat.java (100%) rename {src => term/src/main/java}/jackpal/androidterm/compat/FileCompat.java (100%) rename {src => term/src/main/java}/jackpal/androidterm/compat/MenuItemCompat.java (100%) rename {src => term/src/main/java}/jackpal/androidterm/compat/ServiceForegroundCompat.java (100%) rename {src => term/src/main/java}/jackpal/androidterm/shortcuts/AddShortcut.java (100%) rename {src => term/src/main/java}/jackpal/androidterm/shortcuts/ColorValue.java (100%) rename {src => term/src/main/java}/jackpal/androidterm/shortcuts/FSNavigator.java (100%) rename {src => term/src/main/java}/jackpal/androidterm/shortcuts/TextIcon.java (100%) rename {src => term/src/main/java}/jackpal/androidterm/util/SessionList.java (100%) rename {src => term/src/main/java}/jackpal/androidterm/util/TermSettings.java (100%) rename {jni => term/src/main/jni}/Android.mk (100%) rename {jni => term/src/main/jni}/Application.mk (100%) rename {jni => term/src/main/jni}/common.cpp (100%) rename {jni => term/src/main/jni}/common.h (100%) rename {jni => term/src/main/jni}/fileCompat.cpp (100%) rename {jni => term/src/main/jni}/fileCompat.h (100%) rename {jni => term/src/main/jni}/termExec.cpp (100%) rename {jni => term/src/main/jni}/termExec.h (100%) rename {res => term/src/main/res}/drawable-hdpi-v11/ic_menu_add.png (100%) rename {res => term/src/main/res}/drawable-hdpi-v11/ic_menu_back.png (100%) rename {res => term/src/main/res}/drawable-hdpi-v11/ic_menu_close_clear_cancel.png (100%) rename {res => term/src/main/res}/drawable-hdpi-v11/ic_menu_forward.png (100%) rename {res => term/src/main/res}/drawable-hdpi-v11/ic_menu_preferences.png (100%) rename {res => term/src/main/res}/drawable-hdpi-v11/ic_stat_service_notification_icon.png (100%) rename {res => term/src/main/res}/drawable-hdpi-v9/ic_stat_service_notification_icon.png (100%) rename {res => term/src/main/res}/drawable-hdpi/btn_close_window.png (100%) rename {res => term/src/main/res}/drawable-hdpi/ic_launcher.png (100%) rename {res => term/src/main/res}/drawable-hdpi/ic_menu_add.png (100%) rename {res => term/src/main/res}/drawable-hdpi/ic_menu_back.png (100%) rename {res => term/src/main/res}/drawable-hdpi/ic_menu_close_clear_cancel.png (100%) rename {res => term/src/main/res}/drawable-hdpi/ic_menu_forward.png (100%) rename {res => term/src/main/res}/drawable-hdpi/ic_menu_preferences.png (100%) rename {res => term/src/main/res}/drawable-hdpi/ic_menu_windows.png (100%) rename {res => term/src/main/res}/drawable-hdpi/ic_stat_service_notification_icon.png (100%) rename {res => term/src/main/res}/drawable-ldpi-v11/ic_menu_add.png (100%) rename {res => term/src/main/res}/drawable-ldpi-v11/ic_menu_back.png (100%) rename {res => term/src/main/res}/drawable-ldpi-v11/ic_menu_close_clear_cancel.png (100%) rename {res => term/src/main/res}/drawable-ldpi-v11/ic_menu_forward.png (100%) rename {res => term/src/main/res}/drawable-ldpi-v11/ic_menu_preferences.png (100%) rename {res => term/src/main/res}/drawable-ldpi-v11/ic_stat_service_notification_icon.png (100%) rename {res => term/src/main/res}/drawable-ldpi-v9/ic_stat_service_notification_icon.png (100%) rename {res => term/src/main/res}/drawable-ldpi/ic_launcher.png (100%) rename {res => term/src/main/res}/drawable-ldpi/ic_menu_add.png (100%) rename {res => term/src/main/res}/drawable-ldpi/ic_menu_close_clear_cancel.png (100%) rename {res => term/src/main/res}/drawable-ldpi/ic_menu_preferences.png (100%) rename {res => term/src/main/res}/drawable-ldpi/ic_stat_service_notification_icon.png (100%) rename {res => term/src/main/res}/drawable-mdpi-v11/ic_menu_add.png (100%) rename {res => term/src/main/res}/drawable-mdpi-v11/ic_menu_back.png (100%) rename {res => term/src/main/res}/drawable-mdpi-v11/ic_menu_close_clear_cancel.png (100%) rename {res => term/src/main/res}/drawable-mdpi-v11/ic_menu_forward.png (100%) rename {res => term/src/main/res}/drawable-mdpi-v11/ic_menu_preferences.png (100%) rename {res => term/src/main/res}/drawable-mdpi-v11/ic_stat_service_notification_icon.png (100%) rename {res => term/src/main/res}/drawable-mdpi-v9/ic_stat_service_notification_icon.png (100%) rename {res => term/src/main/res}/drawable-mdpi/btn_close_window.png (100%) rename {res => term/src/main/res}/drawable-mdpi/ic_launcher.png (100%) rename {res => term/src/main/res}/drawable-mdpi/ic_menu_add.png (100%) rename {res => term/src/main/res}/drawable-mdpi/ic_menu_back.png (100%) rename {res => term/src/main/res}/drawable-mdpi/ic_menu_close_clear_cancel.png (100%) rename {res => term/src/main/res}/drawable-mdpi/ic_menu_forward.png (100%) rename {res => term/src/main/res}/drawable-mdpi/ic_menu_preferences.png (100%) rename {res => term/src/main/res}/drawable-mdpi/ic_menu_windows.png (100%) rename {res => term/src/main/res}/drawable-mdpi/ic_stat_service_notification_icon.png (100%) rename {res => term/src/main/res}/drawable-xhdpi-v11/ic_menu_add.png (100%) rename {res => term/src/main/res}/drawable-xhdpi-v11/ic_menu_back.png (100%) rename {res => term/src/main/res}/drawable-xhdpi-v11/ic_menu_close_clear_cancel.png (100%) rename {res => term/src/main/res}/drawable-xhdpi-v11/ic_menu_forward.png (100%) rename {res => term/src/main/res}/drawable-xhdpi-v11/ic_menu_preferences.png (100%) rename {res => term/src/main/res}/drawable-xhdpi-v11/ic_stat_service_notification_icon.png (100%) rename {res => term/src/main/res}/drawable-xhdpi-v9/ic_stat_service_notification_icon.png (100%) rename {res => term/src/main/res}/drawable-xhdpi/ic_launcher.png (100%) rename {res => term/src/main/res}/drawable-xhdpi/ic_stat_service_notification_icon.png (100%) rename {res => term/src/main/res}/drawable-xxhdpi/ic_launcher.png (100%) rename {res => term/src/main/res}/drawable-xxxhdpi/ic_launcher.png (100%) rename {res => term/src/main/res}/drawable/btn_close_window.png (100%) rename {res => term/src/main/res}/drawable/close_background.xml (100%) rename {res => term/src/main/res}/drawable/ic_folder.png (100%) rename {res => term/src/main/res}/drawable/ic_folderup.png (100%) rename {res => term/src/main/res}/drawable/ic_launcher.png (100%) rename {res => term/src/main/res}/drawable/ic_menu_add.png (100%) rename {res => term/src/main/res}/drawable/ic_menu_back.png (100%) rename {res => term/src/main/res}/drawable/ic_menu_close_clear_cancel.png (100%) rename {res => term/src/main/res}/drawable/ic_menu_forward.png (100%) rename {res => term/src/main/res}/drawable/ic_menu_preferences.png (100%) rename {res => term/src/main/res}/drawable/ic_menu_windows.png (100%) rename {res => term/src/main/res}/drawable/ic_stat_service_notification_icon.png (100%) rename {res => term/src/main/res}/layout/term_activity.xml (100%) rename {res => term/src/main/res}/layout/window_list_item.xml (100%) rename {res => term/src/main/res}/layout/window_list_new_window.xml (100%) rename {res => term/src/main/res}/menu/main.xml (100%) rename {res => term/src/main/res}/values-cs/arrays.xml (100%) rename {res => term/src/main/res}/values-cs/strings.xml (100%) rename {res => term/src/main/res}/values-de/arrays.xml (100%) rename {res => term/src/main/res}/values-de/strings.xml (100%) rename {res => term/src/main/res}/values-es/arrays.xml (100%) rename {res => term/src/main/res}/values-es/strings.xml (100%) rename {res => term/src/main/res}/values-eu/arrays.xml (100%) rename {res => term/src/main/res}/values-eu/strings.xml (100%) rename {res => term/src/main/res}/values-fr/arrays.xml (100%) rename {res => term/src/main/res}/values-fr/strings.xml (100%) rename {res => term/src/main/res}/values-hu/arrays.xml (100%) rename {res => term/src/main/res}/values-hu/strings.xml (100%) rename {res => term/src/main/res}/values-it/arrays.xml (100%) rename {res => term/src/main/res}/values-it/strings.xml (100%) rename {res => term/src/main/res}/values-iw/arrays.xml (100%) rename {res => term/src/main/res}/values-iw/strings.xml (100%) rename {res => term/src/main/res}/values-ja/arrays.xml (100%) rename {res => term/src/main/res}/values-ja/strings.xml (100%) rename {res => term/src/main/res}/values-ka/arrays.xml (100%) rename {res => term/src/main/res}/values-ka/strings.xml (100%) rename {res => term/src/main/res}/values-ko/arrays.xml (100%) rename {res => term/src/main/res}/values-ko/strings.xml (100%) rename {res => term/src/main/res}/values-nb/arrays.xml (100%) rename {res => term/src/main/res}/values-nb/strings.xml (100%) rename {res => term/src/main/res}/values-nl/arrays.xml (100%) rename {res => term/src/main/res}/values-nl/strings.xml (100%) rename {res => term/src/main/res}/values-pl/arrays.xml (100%) rename {res => term/src/main/res}/values-pl/strings.xml (100%) rename {res => term/src/main/res}/values-pt-rPT/arrays.xml (100%) rename {res => term/src/main/res}/values-pt-rPT/strings.xml (100%) rename {res => term/src/main/res}/values-pt/arrays.xml (100%) rename {res => term/src/main/res}/values-pt/strings.xml (100%) rename {res => term/src/main/res}/values-ro/arrays.xml (100%) rename {res => term/src/main/res}/values-ro/strings.xml (100%) rename {res => term/src/main/res}/values-ru/arrays.xml (100%) rename {res => term/src/main/res}/values-ru/strings.xml (100%) rename {res => term/src/main/res}/values-sk/arrays.xml (100%) rename {res => term/src/main/res}/values-sk/strings.xml (100%) rename {res => term/src/main/res}/values-sr/arrays.xml (100%) rename {res => term/src/main/res}/values-sr/strings.xml (100%) rename {res => term/src/main/res}/values-sv/arrays.xml (99%) rename {res => term/src/main/res}/values-sv/strings.xml (98%) rename {res => term/src/main/res}/values-tr/arrays.xml (100%) rename {res => term/src/main/res}/values-tr/strings.xml (100%) rename {res => term/src/main/res}/values-uk/arrays.xml (100%) rename {res => term/src/main/res}/values-uk/strings.xml (100%) rename {res => term/src/main/res}/values-zh-rCN/arrays.xml (100%) rename {res => term/src/main/res}/values-zh-rCN/strings.xml (100%) rename {res => term/src/main/res}/values-zh-rTW/arrays.xml (100%) rename {res => term/src/main/res}/values-zh-rTW/strings.xml (100%) rename {res => term/src/main/res}/values/arrays.xml (100%) rename {res => term/src/main/res}/values/arraysNoLocalize.xml (100%) rename {res => term/src/main/res}/values/attrs.xml (100%) rename {res => term/src/main/res}/values/defaults.xml (100%) rename {res => term/src/main/res}/values/id.xml (100%) rename {res => term/src/main/res}/values/strings.xml (100%) rename {res => term/src/main/res}/values/styles.xml (100%) rename {res => term/src/main/res}/xml/preferences.xml (100%) create mode 100644 term/term.iml delete mode 100755 tools/build-debug delete mode 100755 tools/build-release delete mode 100755 tools/install-launcher-icons.sh delete mode 100755 tools/install_service_icons.sh delete mode 100755 tools/mergeTrans.sh delete mode 100755 tools/printFnEscapeSequences delete mode 100755 tools/pushAndRun delete mode 100755 tools/pushAndRun-debug delete mode 100755 tools/runTests delete mode 100644 tools/stuffStrings.go delete mode 100755 tools/update.sh diff --git a/.gitignore b/.gitignore index e7cebe59b..afbdab33e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,6 @@ -bin/ -gen/ -libs/ -obj/ -.classpath -.project -.settings/ -examples/widget/assets/ -pushnote - -# Generated by tools/update.sh -build.xml -local.properties +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 000000000..d6b233251 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +Android-Terminal-Emulator \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 000000000..217af471a --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,23 @@ + + + + + + diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 000000000..e7bedf337 --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 000000000..e206d70d8 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 000000000..bbacae488 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,20 @@ + + + + + + + diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 000000000..989d034f0 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 000000000..376eb940e --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/.idea/scopes/scope_settings.xml b/.idea/scopes/scope_settings.xml new file mode 100644 index 000000000..922003b84 --- /dev/null +++ b/.idea/scopes/scope_settings.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 000000000..def6a6a18 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Android-Terminal-Emulator.iml b/Android-Terminal-Emulator.iml new file mode 100644 index 000000000..da291dfb3 --- /dev/null +++ b/Android-Terminal-Emulator.iml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/Android.mk b/Android.mk deleted file mode 100644 index 073d61122..000000000 --- a/Android.mk +++ /dev/null @@ -1,40 +0,0 @@ -# -# Copyright (C) 2011 Jack Palevich -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -# This makefile shows how to build a shared library and an activity that -# bundles the shared library and calls it using JNI. - -TOP_LOCAL_PATH:= $(call my-dir) - -# Build activity - -LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_SRC_FILES := $(call all-subdir-java-files) - -LOCAL_PACKAGE_NAME := AndroidTerm - -LOCAL_MODULE_TAGS := optional - -LOCAL_REQUIRED_MODULES := libjackpal-androidterm4 - -include $(BUILD_PACKAGE) - -# ============================================================ - -# Also build all of the sub-targets under this one: the shared library. -include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/ant.properties b/ant.properties deleted file mode 100644 index f3714ddf9..000000000 --- a/ant.properties +++ /dev/null @@ -1,19 +0,0 @@ -# This file is used to override default values used by the Ant build system. -# -# This file must be checked in Version Control Systems, as it is -# integral to the build system of your project. - -# This file is only used by the Ant script. - -# You can use this to override default values such as -# 'source.dir' for the location of your java source folder and -# 'out.dir' for the location of your output folder. - -# You can also use it define how the release builds are signed by declaring -# the following properties: -# 'key.store' for the location of your keystore and -# 'key.alias' for the name of the key to use. -# The password will be asked during the build when you use the 'release' target. - -key.alias=jackpal.keystore -key.store=../../Documents/workspace/keystore/jackpal.keystore diff --git a/build.gradle b/build.gradle new file mode 100644 index 000000000..e26cdeef2 --- /dev/null +++ b/build.gradle @@ -0,0 +1,15 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:1.0.0' + } +} + +allprojects { + repositories { + jcenter() + } +} diff --git a/emulatorview/.gitignore b/emulatorview/.gitignore new file mode 100644 index 000000000..796b96d1c --- /dev/null +++ b/emulatorview/.gitignore @@ -0,0 +1 @@ +/build diff --git a/emulatorview/build.gradle b/emulatorview/build.gradle new file mode 100644 index 000000000..77b7f47a8 --- /dev/null +++ b/emulatorview/build.gradle @@ -0,0 +1,18 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 11 + buildToolsVersion "21.1.2" + + defaultConfig { + minSdkVersion 3 + targetSdkVersion 11 + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' + } + } +} diff --git a/emulatorview/emulatorview.iml b/emulatorview/emulatorview.iml new file mode 100644 index 000000000..990d28f88 --- /dev/null +++ b/emulatorview/emulatorview.iml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/libraries/emulatorview/AndroidManifest.xml b/emulatorview/src/main/AndroidManifest.xml similarity index 100% rename from libraries/emulatorview/AndroidManifest.xml rename to emulatorview/src/main/AndroidManifest.xml diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/BaseTextRenderer.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/BaseTextRenderer.java similarity index 100% rename from libraries/emulatorview/src/jackpal/androidterm/emulatorview/BaseTextRenderer.java rename to emulatorview/src/main/java/jackpal/androidterm/emulatorview/BaseTextRenderer.java diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/Bitmap4x8FontRenderer.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/Bitmap4x8FontRenderer.java similarity index 100% rename from libraries/emulatorview/src/jackpal/androidterm/emulatorview/Bitmap4x8FontRenderer.java rename to emulatorview/src/main/java/jackpal/androidterm/emulatorview/Bitmap4x8FontRenderer.java diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/ByteQueue.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/ByteQueue.java similarity index 100% rename from libraries/emulatorview/src/jackpal/androidterm/emulatorview/ByteQueue.java rename to emulatorview/src/main/java/jackpal/androidterm/emulatorview/ByteQueue.java diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/ColorScheme.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/ColorScheme.java similarity index 100% rename from libraries/emulatorview/src/jackpal/androidterm/emulatorview/ColorScheme.java rename to emulatorview/src/main/java/jackpal/androidterm/emulatorview/ColorScheme.java diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/EmulatorDebug.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/EmulatorDebug.java similarity index 100% rename from libraries/emulatorview/src/jackpal/androidterm/emulatorview/EmulatorDebug.java rename to emulatorview/src/main/java/jackpal/androidterm/emulatorview/EmulatorDebug.java diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/EmulatorView.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/EmulatorView.java similarity index 100% rename from libraries/emulatorview/src/jackpal/androidterm/emulatorview/EmulatorView.java rename to emulatorview/src/main/java/jackpal/androidterm/emulatorview/EmulatorView.java diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/GrowableIntArray.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/GrowableIntArray.java similarity index 100% rename from libraries/emulatorview/src/jackpal/androidterm/emulatorview/GrowableIntArray.java rename to emulatorview/src/main/java/jackpal/androidterm/emulatorview/GrowableIntArray.java diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/PaintRenderer.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/PaintRenderer.java similarity index 100% rename from libraries/emulatorview/src/jackpal/androidterm/emulatorview/PaintRenderer.java rename to emulatorview/src/main/java/jackpal/androidterm/emulatorview/PaintRenderer.java diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/Screen.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/Screen.java similarity index 100% rename from libraries/emulatorview/src/jackpal/androidterm/emulatorview/Screen.java rename to emulatorview/src/main/java/jackpal/androidterm/emulatorview/Screen.java diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/StyleRow.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/StyleRow.java similarity index 100% rename from libraries/emulatorview/src/jackpal/androidterm/emulatorview/StyleRow.java rename to emulatorview/src/main/java/jackpal/androidterm/emulatorview/StyleRow.java diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TermKeyListener.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/TermKeyListener.java similarity index 100% rename from libraries/emulatorview/src/jackpal/androidterm/emulatorview/TermKeyListener.java rename to emulatorview/src/main/java/jackpal/androidterm/emulatorview/TermKeyListener.java diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TermSession.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/TermSession.java similarity index 100% rename from libraries/emulatorview/src/jackpal/androidterm/emulatorview/TermSession.java rename to emulatorview/src/main/java/jackpal/androidterm/emulatorview/TermSession.java diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TerminalEmulator.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/TerminalEmulator.java similarity index 100% rename from libraries/emulatorview/src/jackpal/androidterm/emulatorview/TerminalEmulator.java rename to emulatorview/src/main/java/jackpal/androidterm/emulatorview/TerminalEmulator.java diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TextRenderer.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/TextRenderer.java similarity index 100% rename from libraries/emulatorview/src/jackpal/androidterm/emulatorview/TextRenderer.java rename to emulatorview/src/main/java/jackpal/androidterm/emulatorview/TextRenderer.java diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TextStyle.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/TextStyle.java similarity index 100% rename from libraries/emulatorview/src/jackpal/androidterm/emulatorview/TextStyle.java rename to emulatorview/src/main/java/jackpal/androidterm/emulatorview/TextStyle.java diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/TranscriptScreen.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/TranscriptScreen.java similarity index 100% rename from libraries/emulatorview/src/jackpal/androidterm/emulatorview/TranscriptScreen.java rename to emulatorview/src/main/java/jackpal/androidterm/emulatorview/TranscriptScreen.java diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/UnicodeTranscript.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/UnicodeTranscript.java similarity index 100% rename from libraries/emulatorview/src/jackpal/androidterm/emulatorview/UnicodeTranscript.java rename to emulatorview/src/main/java/jackpal/androidterm/emulatorview/UnicodeTranscript.java diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/UpdateCallback.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/UpdateCallback.java similarity index 100% rename from libraries/emulatorview/src/jackpal/androidterm/emulatorview/UpdateCallback.java rename to emulatorview/src/main/java/jackpal/androidterm/emulatorview/UpdateCallback.java diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/compat/AndroidCharacterCompat.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/compat/AndroidCharacterCompat.java similarity index 100% rename from libraries/emulatorview/src/jackpal/androidterm/emulatorview/compat/AndroidCharacterCompat.java rename to emulatorview/src/main/java/jackpal/androidterm/emulatorview/compat/AndroidCharacterCompat.java diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/compat/AndroidCompat.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/compat/AndroidCompat.java similarity index 100% rename from libraries/emulatorview/src/jackpal/androidterm/emulatorview/compat/AndroidCompat.java rename to emulatorview/src/main/java/jackpal/androidterm/emulatorview/compat/AndroidCompat.java diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/compat/ClipboardManagerCompat.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/compat/ClipboardManagerCompat.java similarity index 100% rename from libraries/emulatorview/src/jackpal/androidterm/emulatorview/compat/ClipboardManagerCompat.java rename to emulatorview/src/main/java/jackpal/androidterm/emulatorview/compat/ClipboardManagerCompat.java diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/compat/ClipboardManagerCompatFactory.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/compat/ClipboardManagerCompatFactory.java similarity index 100% rename from libraries/emulatorview/src/jackpal/androidterm/emulatorview/compat/ClipboardManagerCompatFactory.java rename to emulatorview/src/main/java/jackpal/androidterm/emulatorview/compat/ClipboardManagerCompatFactory.java diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/compat/ClipboardManagerCompatV1.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/compat/ClipboardManagerCompatV1.java similarity index 100% rename from libraries/emulatorview/src/jackpal/androidterm/emulatorview/compat/ClipboardManagerCompatV1.java rename to emulatorview/src/main/java/jackpal/androidterm/emulatorview/compat/ClipboardManagerCompatV1.java diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/compat/ClipboardManagerCompatV11.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/compat/ClipboardManagerCompatV11.java similarity index 100% rename from libraries/emulatorview/src/jackpal/androidterm/emulatorview/compat/ClipboardManagerCompatV11.java rename to emulatorview/src/main/java/jackpal/androidterm/emulatorview/compat/ClipboardManagerCompatV11.java diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/compat/KeyCharacterMapCompat.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/compat/KeyCharacterMapCompat.java similarity index 100% rename from libraries/emulatorview/src/jackpal/androidterm/emulatorview/compat/KeyCharacterMapCompat.java rename to emulatorview/src/main/java/jackpal/androidterm/emulatorview/compat/KeyCharacterMapCompat.java diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/compat/KeycodeConstants.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/compat/KeycodeConstants.java similarity index 100% rename from libraries/emulatorview/src/jackpal/androidterm/emulatorview/compat/KeycodeConstants.java rename to emulatorview/src/main/java/jackpal/androidterm/emulatorview/compat/KeycodeConstants.java diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/compat/Patterns.java b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/compat/Patterns.java similarity index 100% rename from libraries/emulatorview/src/jackpal/androidterm/emulatorview/compat/Patterns.java rename to emulatorview/src/main/java/jackpal/androidterm/emulatorview/compat/Patterns.java diff --git a/libraries/emulatorview/src/jackpal/androidterm/emulatorview/package.html b/emulatorview/src/main/java/jackpal/androidterm/emulatorview/package.html similarity index 100% rename from libraries/emulatorview/src/jackpal/androidterm/emulatorview/package.html rename to emulatorview/src/main/java/jackpal/androidterm/emulatorview/package.html diff --git a/libraries/emulatorview/res/drawable-nodpi/atari_small_nodpi.png b/emulatorview/src/main/res/drawable-nodpi/atari_small_nodpi.png similarity index 100% rename from libraries/emulatorview/res/drawable-nodpi/atari_small_nodpi.png rename to emulatorview/src/main/res/drawable-nodpi/atari_small_nodpi.png diff --git a/libraries/emulatorview/res/drawable/atari_small.png b/emulatorview/src/main/res/drawable/atari_small.png similarity index 100% rename from libraries/emulatorview/res/drawable/atari_small.png rename to emulatorview/src/main/res/drawable/atari_small.png diff --git a/examples/intents/AndroidManifest.xml b/examples/intents/AndroidManifest.xml deleted file mode 100644 index 1645991cc..000000000 --- a/examples/intents/AndroidManifest.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - diff --git a/examples/intents/ant.properties b/examples/intents/ant.properties deleted file mode 100644 index b0971e891..000000000 --- a/examples/intents/ant.properties +++ /dev/null @@ -1,17 +0,0 @@ -# This file is used to override default values used by the Ant build system. -# -# This file must be checked into Version Control Systems, as it is -# integral to the build system of your project. - -# This file is only used by the Ant script. - -# You can use this to override default values such as -# 'source.dir' for the location of your java source folder and -# 'out.dir' for the location of your output folder. - -# You can also use it define how the release builds are signed by declaring -# the following properties: -# 'key.store' for the location of your keystore and -# 'key.alias' for the name of the key to use. -# The password will be asked during the build when you use the 'release' target. - diff --git a/examples/intents/proguard-project.txt b/examples/intents/proguard-project.txt deleted file mode 100644 index f2fe1559a..000000000 --- a/examples/intents/proguard-project.txt +++ /dev/null @@ -1,20 +0,0 @@ -# To enable ProGuard in your project, edit project.properties -# to define the proguard.config property as described in that file. -# -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in ${sdk.dir}/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the ProGuard -# include property in project.properties. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} diff --git a/examples/intents/project.properties b/examples/intents/project.properties deleted file mode 100644 index 4d07452b5..000000000 --- a/examples/intents/project.properties +++ /dev/null @@ -1,14 +0,0 @@ -# This file is automatically generated by Android Tools. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# -# This file must be checked in Version Control Systems. -# -# To customize properties used by the Ant build system edit -# "ant.properties", and override values to adapt the script to your -# project structure. -# -# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): -#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt - -# Project target. -target=android-11 diff --git a/examples/intents/res/layout/main.xml b/examples/intents/res/layout/main.xml deleted file mode 100644 index 96ea176e4..000000000 --- a/examples/intents/res/layout/main.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - -

`SgR&(w` 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