diff options
-rw-r--r-- | AndroidManifest.xml | 5 | ||||
-rw-r--r-- | jni/Android.mk | 9 | ||||
-rw-r--r-- | jni/com_android_terminal_Terminal.cpp | 404 | ||||
-rw-r--r-- | res/layout/activity.xml | 3 | ||||
-rw-r--r-- | src/com/android/terminal/Terminal.java | 56 | ||||
-rw-r--r-- | src/com/android/terminal/TerminalActivity.java | 48 | ||||
-rw-r--r-- | src/com/android/terminal/TerminalCallbacks.java | 8 | ||||
-rw-r--r-- | src/com/android/terminal/TerminalKeys.java | 2 | ||||
-rw-r--r-- | src/com/android/terminal/TerminalLineView.java | 85 | ||||
-rw-r--r-- | src/com/android/terminal/TerminalService.java | 30 | ||||
-rw-r--r-- | src/com/android/terminal/TerminalView.java | 354 |
11 files changed, 625 insertions, 379 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 734af7f..7cbdc1c 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -21,8 +21,9 @@ android:persistent="true" android:enabled="false"> - <activity android:name=".TerminalActivity" - android:windowSoftInputMode="stateAlwaysVisible|adjustResize"> + <activity + android:name=".TerminalActivity" + android:windowSoftInputMode="stateAlwaysVisible|adjustResize"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.DEFAULT" /> diff --git a/jni/Android.mk b/jni/Android.mk index fb4721a..1be6426 100644 --- a/jni/Android.mk +++ b/jni/Android.mk @@ -9,12 +9,15 @@ LOCAL_SRC_FILES := \ LOCAL_C_INCLUDES += \ external/libvterm/include \ - libcore/include + libcore/include \ + frameworks/base/include LOCAL_SHARED_LIBRARIES := \ + libandroidfw \ + libandroid_runtime \ liblog \ - libutils \ - libnativehelper + libnativehelper \ + libutils LOCAL_STATIC_LIBRARIES := \ libvterm diff --git a/jni/com_android_terminal_Terminal.cpp b/jni/com_android_terminal_Terminal.cpp index 2829761..8260316 100644 --- a/jni/com_android_terminal_Terminal.cpp +++ b/jni/com_android_terminal_Terminal.cpp @@ -17,6 +17,9 @@ #define LOG_TAG "Terminal" #include <utils/Log.h> +#include <utils/Mutex.h> +#include "android_runtime/AndroidRuntime.h" + #include "forkpty.h" #include "jni.h" #include "JNIHelp.h" @@ -37,15 +40,11 @@ #define USE_TEST_SHELL 0 #define DEBUG_CALLBACKS 0 #define DEBUG_IO 0 +#define DEBUG_SCROLLBACK 0 namespace android { /* - * JavaVM reference - */ -static JavaVM* gJavaVM; - -/* * Callback class reference */ static jclass terminalCallbacksClass; @@ -54,7 +53,6 @@ static jclass terminalCallbacksClass; * Callback methods */ static jmethodID damageMethod; -static jmethodID prescrollMethod; static jmethodID moveRectMethod; static jmethodID moveCursorMethod; static jmethodID setTermPropBooleanMethod; @@ -62,7 +60,6 @@ static jmethodID setTermPropIntMethod; static jmethodID setTermPropStringMethod; static jmethodID setTermPropColorMethod; static jmethodID bellMethod; -static jmethodID resizeMethod; /* * CellRun class @@ -74,16 +71,48 @@ static jfieldID cellRunColSizeField; static jfieldID cellRunFgField; static jfieldID cellRunBgField; +typedef short unsigned int dimen_t; + +class ScrollbackLine { +public: + inline ScrollbackLine(dimen_t _cols) : cols(_cols) { + mCells = new VTermScreenCell[cols]; + }; + inline ~ScrollbackLine() { + delete mCells; + } + + inline dimen_t copyFrom(dimen_t cols, const VTermScreenCell* cells) { + dimen_t n = this->cols > cols ? cols : this->cols; + memcpy(mCells, cells, sizeof(VTermScreenCell) * n); + return n; + } + + inline dimen_t copyTo(dimen_t cols, VTermScreenCell* cells) { + dimen_t n = cols > this->cols ? this->cols : cols; + memcpy(cells, mCells, sizeof(VTermScreenCell) * n); + return n; + } + + inline void getCell(dimen_t col, VTermScreenCell* cell) { + *cell = mCells[col]; + } + + const dimen_t cols; + +private: + VTermScreenCell* mCells; +}; + /* * Terminal session */ class Terminal { public: - Terminal(jobject callbacks, int rows, int cols); + Terminal(jobject callbacks, dimen_t rows, dimen_t cols); ~Terminal(); - int run(); - int stop(); + status_t run(); size_t write(const char *bytes, size_t len); @@ -91,36 +120,39 @@ public: bool dispatchKey(int mod, int key); bool flushInput(); - int flushDamage(); - int resize(short unsigned int rows, short unsigned int cols); + status_t resize(dimen_t rows, dimen_t cols, dimen_t scrollRows); + + status_t onPushline(dimen_t cols, const VTermScreenCell* cells); + status_t onPopline(dimen_t cols, VTermScreenCell* cells); - int getCell(VTermPos pos, VTermScreenCell* cell); + void getCellLocked(VTermPos pos, VTermScreenCell* cell); - int getRows() const; - int getCols() const; + dimen_t getRows() const; + dimen_t getCols() const; + dimen_t getScrollRows() const; jobject getCallbacks() const; + // Lock protecting mutations of internal libvterm state + Mutex mLock; + private: int mMasterFd; + pid_t mChildPid; VTerm *mVt; VTermScreen *mVts; jobject mCallbacks; - short unsigned int mRows; - short unsigned int mCols; - bool mStopped; -}; -static JNIEnv* getEnv() { - JNIEnv* env; + dimen_t mRows; + dimen_t mCols; + bool mKilled; - if (gJavaVM->AttachCurrentThread(&env, NULL) < 0) { - return NULL; - } + ScrollbackLine **mScroll; + dimen_t mScrollCur; + dimen_t mScrollSize; - return env; -} +}; /* * VTerm event handlers @@ -132,44 +164,18 @@ static int term_damage(VTermRect rect, void *user) { ALOGW("term_damage"); #endif - JNIEnv* env = getEnv(); - if (env == NULL) { - ALOGE("term_damage: couldn't get JNIEnv"); - return 0; - } - + JNIEnv* env = AndroidRuntime::getJNIEnv(); return env->CallIntMethod(term->getCallbacks(), damageMethod, rect.start_row, rect.end_row, rect.start_col, rect.end_col); } -static int term_prescroll(VTermRect rect, void *user) { - Terminal* term = reinterpret_cast<Terminal*>(user); -#if DEBUG_CALLBACKS - ALOGW("term_prescroll"); -#endif - - JNIEnv* env = getEnv(); - if (env == NULL) { - ALOGE("term_prescroll: couldn't get JNIEnv"); - return 0; - } - - return env->CallIntMethod(term->getCallbacks(), prescrollMethod, rect.start_row, rect.end_row, - rect.start_col, rect.end_col); -} - static int term_moverect(VTermRect dest, VTermRect src, void *user) { Terminal* term = reinterpret_cast<Terminal*>(user); #if DEBUG_CALLBACKS ALOGW("term_moverect"); #endif - JNIEnv* env = getEnv(); - if (env == NULL) { - ALOGE("term_moverect: couldn't get JNIEnv"); - return 0; - } - + JNIEnv* env = AndroidRuntime::getJNIEnv(); return env->CallIntMethod(term->getCallbacks(), moveRectMethod, dest.start_row, dest.end_row, dest.start_col, dest.end_col, src.start_row, src.end_row, src.start_col, src.end_col); @@ -181,12 +187,7 @@ static int term_movecursor(VTermPos pos, VTermPos oldpos, int visible, void *use ALOGW("term_movecursor"); #endif - JNIEnv* env = getEnv(); - if (env == NULL) { - ALOGE("term_movecursor: couldn't get JNIEnv"); - return 0; - } - + JNIEnv* env = AndroidRuntime::getJNIEnv(); return env->CallIntMethod(term->getCallbacks(), moveCursorMethod, pos.row, pos.col, oldpos.row, oldpos.col, visible); } @@ -197,12 +198,7 @@ static int term_settermprop(VTermProp prop, VTermValue *val, void *user) { ALOGW("term_settermprop"); #endif - JNIEnv* env = getEnv(); - if (env == NULL) { - ALOGE("term_settermprop: couldn't get JNIEnv"); - return 0; - } - + JNIEnv* env = AndroidRuntime::getJNIEnv(); switch (vterm_get_prop_type(prop)) { case VTERM_VALUETYPE_BOOL: return env->CallIntMethod(term->getCallbacks(), setTermPropBooleanMethod, @@ -235,43 +231,50 @@ static int term_bell(void *user) { ALOGW("term_bell"); #endif - JNIEnv* env = getEnv(); - if (env == NULL) { - ALOGE("term_bell: couldn't get JNIEnv"); - return 0; - } - + JNIEnv* env = AndroidRuntime::getJNIEnv(); return env->CallIntMethod(term->getCallbacks(), bellMethod); } -static int term_resize(int rows, int cols, void *user) { +static int term_sb_pushline(int cols, const VTermScreenCell *cells, void *user) { Terminal* term = reinterpret_cast<Terminal*>(user); #if DEBUG_CALLBACKS - ALOGW("term_resize"); + ALOGW("term_sb_pushline"); #endif - JNIEnv* env = getEnv(); - if (env == NULL) { - ALOGE("term_bell: couldn't get JNIEnv"); - return 0; - } + return term->onPushline(cols, cells); +} - return env->CallIntMethod(term->getCallbacks(), resizeMethod, rows, cols); +static int term_sb_popline(int cols, VTermScreenCell *cells, void *user) { + Terminal* term = reinterpret_cast<Terminal*>(user); +#if DEBUG_CALLBACKS + ALOGW("term_sb_popline"); +#endif + + return term->onPopline(cols, cells); } static VTermScreenCallbacks cb = { .damage = term_damage, - .prescroll = term_prescroll, .moverect = term_moverect, .movecursor = term_movecursor, .settermprop = term_settermprop, .setmousefunc = term_setmousefunc, .bell = term_bell, - .resize = term_resize, + // Resize requests are applied immediately, so callback is ignored + .resize = NULL, + .sb_pushline = term_sb_pushline, + .sb_popline = term_sb_popline, }; -Terminal::Terminal(jobject callbacks, int rows, int cols) : - mCallbacks(callbacks), mRows(rows), mCols(cols), mStopped(false) { +Terminal::Terminal(jobject callbacks, dimen_t rows, dimen_t cols) : + mCallbacks(callbacks), mRows(rows), mCols(cols), mKilled(false), + mScrollCur(0), mScrollSize(100) { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + mCallbacks = env->NewGlobalRef(callbacks); + + mScroll = new ScrollbackLine*[mScrollSize]; + memset(mScroll, 0, sizeof(ScrollbackLine*) * mScrollSize); + /* Create VTerm */ mVt = vterm_new(rows, cols); vterm_parser_set_utf8(mVt, 1); @@ -286,10 +289,17 @@ Terminal::Terminal(jobject callbacks, int rows, int cols) : Terminal::~Terminal() { close(mMasterFd); + ::kill(mChildPid, SIGHUP); + vterm_free(mVt); + + delete mScroll; + + JNIEnv *env = AndroidRuntime::getJNIEnv(); + env->DeleteGlobalRef(mCallbacks); } -int Terminal::run() { +status_t Terminal::run() { struct termios termios = { .c_iflag = ICRNL|IXON|IUTF8, .c_oflag = OPOST|ONLCR|NL0|CR0|TAB0|BS0|VT0|FF0, @@ -322,8 +332,8 @@ int Terminal::run() { ALOGE("failed to dup stderr - %s", strerror(errno)); } - pid_t kid = forkpty(&mMasterFd, NULL, &termios, &size); - if (kid == 0) { + mChildPid = forkpty(&mMasterFd, NULL, &termios, &size); + if (mChildPid == 0) { /* Restore the ISIG signals back to defaults */ signal(SIGINT, SIG_DFL); signal(SIGQUIT, SIG_DFL); @@ -338,7 +348,7 @@ int Terminal::run() { char *shell = "/system/bin/sh"; //getenv("SHELL"); #if USE_TEST_SHELL - char *args[4] = {shell, "-c", "x=1; c=0; while true; do echo -e \"stop \e[00;3${c}mechoing\e[00m yourself! ($x)\"; x=$(( $x + 1 )); c=$((($c+1)%7)); sleep 0.5; done", NULL}; + char *args[4] = {shell, "-c", "x=1; c=0; while true; do echo -e \"stop \e[00;3${c}mechoing\e[00m yourself! ($x)\"; x=$(( $x + 1 )); c=$((($c+1)%7)); if [ $x -gt 110 ]; then sleep 0.5; fi; done", NULL}; #else char *args[2] = {shell, NULL}; #endif @@ -356,8 +366,8 @@ int Terminal::run() { ALOGD("read() returned %d bytes", bytes); #endif - if (mStopped) { - ALOGD("stop() requested"); + if (mKilled) { + ALOGD("kill() requested"); break; } if (bytes == 0) { @@ -369,30 +379,28 @@ int Terminal::run() { return 1; } - vterm_push_bytes(mVt, buffer, bytes); - - vterm_screen_flush_damage(mVts); + { + Mutex::Autolock lock(mLock); + vterm_push_bytes(mVt, buffer, bytes); + vterm_screen_flush_damage(mVts); + } } return 0; } -int Terminal::stop() { - // TODO: explicitly kill forked child process - mStopped = true; - return 0; -} - size_t Terminal::write(const char *bytes, size_t len) { return ::write(mMasterFd, bytes, len); } bool Terminal::dispatchCharacter(int mod, int character) { + Mutex::Autolock lock(mLock); vterm_input_push_char(mVt, static_cast<VTermModifier>(mod), character); return flushInput(); } bool Terminal::dispatchKey(int mod, int key) { + Mutex::Autolock lock(mLock); vterm_input_push_key(mVt, static_cast<VTermModifier>(mod), static_cast<VTermKey>(key)); return flushInput(); } @@ -407,16 +415,14 @@ bool Terminal::flushInput() { return true; } -int Terminal::flushDamage() { - vterm_screen_flush_damage(mVts); - return 0; -} +status_t Terminal::resize(dimen_t rows, dimen_t cols, dimen_t scrollRows) { + Mutex::Autolock lock(mLock); -int Terminal::resize(short unsigned int rows, short unsigned int cols) { - ALOGD("resize(%d, %d)", rows, cols); + ALOGD("resize(%d, %d, %d)", rows, cols, scrollRows); mRows = rows; mCols = cols; + // TODO: resize scrollback struct winsize size = { rows, cols, 0, 0 }; ioctl(mMasterFd, TIOCSWINSZ, &size); @@ -427,18 +433,115 @@ int Terminal::resize(short unsigned int rows, short unsigned int cols) { return 0; } -int Terminal::getCell(VTermPos pos, VTermScreenCell* cell) { - return vterm_screen_get_cell(mVts, pos, cell); +status_t Terminal::onPushline(dimen_t cols, const VTermScreenCell* cells) { + ScrollbackLine* line = NULL; + if (mScrollCur == mScrollSize) { + /* Recycle old row if it's the right size */ + if (mScroll[mScrollCur - 1]->cols == cols) { + line = mScroll[mScrollCur - 1]; + } else { + delete mScroll[mScrollCur - 1]; + } + + memmove(mScroll + 1, mScroll, sizeof(ScrollbackLine*) * (mScrollCur - 1)); + } else if (mScrollCur > 0) { + memmove(mScroll + 1, mScroll, sizeof(ScrollbackLine*) * mScrollCur); + } + + if (line == NULL) { + line = new ScrollbackLine(cols); + } + + mScroll[0] = line; + + if (mScrollCur < mScrollSize) { + mScrollCur++; + } + + line->copyFrom(cols, cells); + return 1; } -int Terminal::getRows() const { +status_t Terminal::onPopline(dimen_t cols, VTermScreenCell* cells) { + if (mScrollCur == 0) { + return 0; + } + + ScrollbackLine* line = mScroll[0]; + mScrollCur--; + memmove(mScroll, mScroll + 1, sizeof(ScrollbackLine*) * mScrollCur); + + dimen_t n = line->copyTo(cols, cells); + for (dimen_t col = n; col < cols; col++) { + cells[col].chars[0] = 0; + cells[col].width = 1; + } + + delete line; + return 1; +} + +void Terminal::getCellLocked(VTermPos pos, VTermScreenCell* cell) { + // The UI may be asking for cell data while the model is changing + // underneath it, so we always fill with meaningful data. + + if (pos.row < 0) { + size_t scrollRow = -pos.row; + if (scrollRow > mScrollCur) { + // Invalid region above current scrollback + cell->width = 1; +#if DEBUG_SCROLLBACK + cell->bg.red = 255; +#endif + return; + } + + ScrollbackLine* line = mScroll[scrollRow - 1]; + if ((size_t) pos.col < line->cols) { + // Valid scrollback cell + line->getCell(pos.col, cell); + cell->width = 1; +#if DEBUG_SCROLLBACK + cell->bg.blue = 255; +#endif + return; + } else { + // Extend last scrollback cell into invalid region + line->getCell(line->cols - 1, cell); + cell->width = 1; + cell->chars[0] = ' '; +#if DEBUG_SCROLLBACK + cell->bg.green = 255; +#endif + return; + } + } + + if ((size_t) pos.row >= mRows) { + // Invalid region below screen + cell->width = 1; +#if DEBUG_SCROLLBACK + cell->bg.red = 128; +#endif + return; + } + + // Valid screen cell + vterm_screen_get_cell(mVts, pos, cell); +} + +dimen_t Terminal::getRows() const { return mRows; } -int Terminal::getCols() const { +dimen_t Terminal::getCols() const { return mCols; } +dimen_t Terminal::getScrollRows() const { + return mScrollSize; +} + jobject Terminal::getCallbacks() const { return mCallbacks; } @@ -449,48 +552,49 @@ jobject Terminal::getCallbacks() const { static jint com_android_terminal_Terminal_nativeInit(JNIEnv* env, jclass clazz, jobject callbacks, jint rows, jint cols) { - return reinterpret_cast<jint>(new Terminal(env->NewGlobalRef(callbacks), rows, cols)); -} - -static jint com_android_terminal_Terminal_nativeRun(JNIEnv* env, jclass clazz, jint ptr) { - Terminal* term = reinterpret_cast<Terminal*>(ptr); - return term->run(); + return reinterpret_cast<jint>(new Terminal(callbacks, rows, cols)); } -static jint com_android_terminal_Terminal_nativeStop(JNIEnv* env, jclass clazz, jint ptr) { +static jint com_android_terminal_Terminal_nativeDestroy(JNIEnv* env, jclass clazz, jint ptr) { Terminal* term = reinterpret_cast<Terminal*>(ptr); - return term->stop(); + delete term; + return 0; } -static jint com_android_terminal_Terminal_nativeFlushDamage(JNIEnv* env, jclass clazz, jint ptr) { +static jint com_android_terminal_Terminal_nativeRun(JNIEnv* env, jclass clazz, jint ptr) { Terminal* term = reinterpret_cast<Terminal*>(ptr); - return term->flushDamage(); + return term->run(); } static jint com_android_terminal_Terminal_nativeResize(JNIEnv* env, - jclass clazz, jint ptr, jint rows, jint cols) { + jclass clazz, jint ptr, jint rows, jint cols, jint scrollRows) { Terminal* term = reinterpret_cast<Terminal*>(ptr); - return term->resize(rows, cols); + return term->resize(rows, cols, scrollRows); } -static int toArgb(VTermColor* color) { - return 0xff << 24 | color->red << 16 | color->green << 8 | color->blue; +static inline int toArgb(const VTermColor& color) { + return (0xff << 24 | color.red << 16 | color.green << 8 | color.blue); } -static bool isCellStyleEqual(VTermScreenCell* a, VTermScreenCell* b) { - // TODO: check other attrs beyond just color - if (toArgb(&a->fg) != toArgb(&b->fg)) { - return false; - } - if (toArgb(&a->bg) != toArgb(&b->bg)) { - return false; - } +static inline bool isCellStyleEqual(const VTermScreenCell& a, const VTermScreenCell& b) { + if (toArgb(a.fg) != toArgb(b.fg)) return false; + if (toArgb(a.bg) != toArgb(b.bg)) return false; + + if (a.attrs.bold != b.attrs.bold) return false; + if (a.attrs.underline != b.attrs.underline) return false; + if (a.attrs.italic != b.attrs.italic) return false; + if (a.attrs.blink != b.attrs.blink) return false; + if (a.attrs.reverse != b.attrs.reverse) return false; + if (a.attrs.strike != b.attrs.strike) return false; + if (a.attrs.font != b.attrs.font) return false; + return true; } static jint com_android_terminal_Terminal_nativeGetCellRun(JNIEnv* env, jclass clazz, jint ptr, jint row, jint col, jobject run) { Terminal* term = reinterpret_cast<Terminal*>(ptr); + Mutex::Autolock lock(term->mLock); jcharArray dataArray = (jcharArray) env->GetObjectField(run, cellRunDataField); ScopedCharArrayRW data(env, dataArray); @@ -498,33 +602,32 @@ static jint com_android_terminal_Terminal_nativeGetCellRun(JNIEnv* env, return -1; } - VTermScreenCell prevCell, cell; - memset(&prevCell, 0, sizeof(VTermScreenCell)); - memset(&cell, 0, sizeof(VTermScreenCell)); + VTermScreenCell firstCell, cell; VTermPos pos = { .row = row, .col = col, }; - unsigned int dataSize = 0; - unsigned int colSize = 0; - while (pos.col < term->getCols()) { - int res = term->getCell(pos, &cell); + size_t dataSize = 0; + size_t colSize = 0; + while ((size_t) pos.col < term->getCols()) { + memset(&cell, 0, sizeof(VTermScreenCell)); + term->getCellLocked(pos, &cell); if (colSize == 0) { - env->SetIntField(run, cellRunFgField, toArgb(&cell.fg)); - env->SetIntField(run, cellRunBgField, toArgb(&cell.bg)); + env->SetIntField(run, cellRunFgField, toArgb(cell.fg)); + env->SetIntField(run, cellRunBgField, toArgb(cell.bg)); + memcpy(&firstCell, &cell, sizeof(VTermScreenCell)); } else { - if (!isCellStyleEqual(&cell, &prevCell)) { + if (!isCellStyleEqual(cell, firstCell)) { break; } } - memcpy(&prevCell, &cell, sizeof(VTermScreenCell)); // Only include cell chars if they fit into run uint32_t rawCell = cell.chars[0]; - unsigned int size = (rawCell < 0x10000) ? 1 : 2; + size_t size = (rawCell < 0x10000) ? 1 : 2; if (dataSize + size <= data.size()) { if (rawCell < 0x10000) { data[dataSize++] = rawCell; @@ -560,6 +663,11 @@ static jint com_android_terminal_Terminal_nativeGetCols(JNIEnv* env, jclass claz return term->getCols(); } +static jint com_android_terminal_Terminal_nativeGetScrollRows(JNIEnv* env, jclass clazz, jint ptr) { + Terminal* term = reinterpret_cast<Terminal*>(ptr); + return term->getScrollRows(); +} + static jboolean com_android_terminal_Terminal_nativeDispatchCharacter(JNIEnv *env, jclass clazz, jint ptr, jint mod, jint c) { Terminal* term = reinterpret_cast<Terminal*>(ptr); @@ -574,13 +682,13 @@ static jboolean com_android_terminal_Terminal_nativeDispatchKey(JNIEnv *env, jcl static JNINativeMethod gMethods[] = { { "nativeInit", "(Lcom/android/terminal/TerminalCallbacks;II)I", (void*)com_android_terminal_Terminal_nativeInit }, + { "nativeDestroy", "(I)I", (void*)com_android_terminal_Terminal_nativeDestroy }, { "nativeRun", "(I)I", (void*)com_android_terminal_Terminal_nativeRun }, - { "nativeStop", "(I)I", (void*)com_android_terminal_Terminal_nativeStop }, - { "nativeFlushDamage", "(I)I", (void*)com_android_terminal_Terminal_nativeFlushDamage }, - { "nativeResize", "(III)I", (void*)com_android_terminal_Terminal_nativeResize }, + { "nativeResize", "(IIII)I", (void*)com_android_terminal_Terminal_nativeResize }, { "nativeGetCellRun", "(IIILcom/android/terminal/Terminal$CellRun;)I", (void*)com_android_terminal_Terminal_nativeGetCellRun }, { "nativeGetRows", "(I)I", (void*)com_android_terminal_Terminal_nativeGetRows }, { "nativeGetCols", "(I)I", (void*)com_android_terminal_Terminal_nativeGetCols }, + { "nativeGetScrollRows", "(I)I", (void*)com_android_terminal_Terminal_nativeGetScrollRows }, { "nativeDispatchCharacter", "(III)Z", (void*)com_android_terminal_Terminal_nativeDispatchCharacter}, { "nativeDispatchKey", "(III)Z", (void*)com_android_terminal_Terminal_nativeDispatchKey }, }; @@ -592,7 +700,6 @@ int register_com_android_terminal_Terminal(JNIEnv* env) { android::terminalCallbacksClass = reinterpret_cast<jclass>(env->NewGlobalRef(localClass.get())); android::damageMethod = env->GetMethodID(terminalCallbacksClass, "damage", "(IIII)I"); - android::prescrollMethod = env->GetMethodID(terminalCallbacksClass, "prescroll", "(IIII)I"); android::moveRectMethod = env->GetMethodID(terminalCallbacksClass, "moveRect", "(IIIIIIII)I"); android::moveCursorMethod = env->GetMethodID(terminalCallbacksClass, "moveCursor", "(IIIII)I"); @@ -605,7 +712,6 @@ int register_com_android_terminal_Terminal(JNIEnv* env) { android::setTermPropColorMethod = env->GetMethodID(terminalCallbacksClass, "setTermPropColor", "(IIII)I"); android::bellMethod = env->GetMethodID(terminalCallbacksClass, "bell", "()I"); - android::resizeMethod = env->GetMethodID(terminalCallbacksClass, "resize", "(II)I"); ScopedLocalRef<jclass> cellRunLocal(env, env->FindClass("com/android/terminal/Terminal$CellRun")); @@ -616,8 +722,6 @@ int register_com_android_terminal_Terminal(JNIEnv* env) { cellRunFgField = env->GetFieldID(cellRunClass, "fg", "I"); cellRunBgField = env->GetFieldID(cellRunClass, "bg", "I"); - env->GetJavaVM(&gJavaVM); - return jniRegisterNativeMethods(env, "com/android/terminal/Terminal", gMethods, NELEM(gMethods)); } diff --git a/res/layout/activity.xml b/res/layout/activity.xml index 1ec603d..2138c5a 100644 --- a/res/layout/activity.xml +++ b/res/layout/activity.xml @@ -24,7 +24,6 @@ android:id="@+id/titles" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_gravity="top" - /> + android:layout_gravity="top" /> </android.support.v4.view.ViewPager> diff --git a/src/com/android/terminal/Terminal.java b/src/com/android/terminal/Terminal.java index 35c2cee..cdf3f60 100644 --- a/src/com/android/terminal/Terminal.java +++ b/src/com/android/terminal/Terminal.java @@ -22,7 +22,9 @@ import android.graphics.Color; * Single terminal session backed by a pseudo terminal on the local device. */ public class Terminal { - private static final String TAG = "Terminal"; + public static final String TAG = "Terminal"; + + public final int key; private static int sNumber = 0; @@ -46,15 +48,17 @@ public class Terminal { boolean strike; int font; - int fg = Color.RED; - int bg = Color.BLUE; + int fg = Color.CYAN; + int bg = Color.DKGRAY; } + // NOTE: clients must not call back into terminal while handling a callback, + // since native mutex isn't reentrant. public interface TerminalClient { - public void damage(int startRow, int endRow, int startCol, int endCol); - public void moveRect(int destStartRow, int destEndRow, int destStartCol, int destEndCol, + public void onDamage(int startRow, int endRow, int startCol, int endCol); + public void onMoveRect(int destStartRow, int destEndRow, int destStartCol, int destEndCol, int srcStartRow, int srcEndRow, int srcStartCol, int srcEndCol); - public void bell(); + public void onBell(); } private final int mNativePtr; @@ -68,7 +72,7 @@ public class Terminal { @Override public int damage(int startRow, int endRow, int startCol, int endCol) { if (mClient != null) { - mClient.damage(startRow, endRow, startCol, endCol); + mClient.onDamage(startRow, endRow, startCol, endCol); } return 1; } @@ -77,7 +81,7 @@ public class Terminal { public int moveRect(int destStartRow, int destEndRow, int destStartCol, int destEndCol, int srcStartRow, int srcEndRow, int srcStartCol, int srcEndCol) { if (mClient != null) { - mClient.moveRect(destStartRow, destEndRow, destStartCol, destEndCol, srcStartRow, + mClient.onMoveRect(destStartRow, destEndRow, destStartCol, destEndCol, srcStartRow, srcEndRow, srcStartCol, srcEndCol); } return 1; @@ -86,7 +90,7 @@ public class Terminal { @Override public int bell() { if (mClient != null) { - mClient.bell(); + mClient.onBell(); } return 1; } @@ -94,8 +98,9 @@ public class Terminal { public Terminal() { mNativePtr = nativeInit(mCallbacks, 25, 80); - mTitle = TAG + " " + sNumber++; - mThread = new Thread(TAG) { + key = sNumber++; + mTitle = TAG + " " + key; + mThread = new Thread(mTitle) { @Override public void run() { nativeRun(mNativePtr); @@ -110,9 +115,9 @@ public class Terminal { mThread.start(); } - public void stop() { - if (nativeStop(mNativePtr) != 0) { - throw new IllegalStateException("stop failed"); + public void destroy() { + if (nativeDestroy(mNativePtr) != 0) { + throw new IllegalStateException("destroy failed"); } } @@ -120,14 +125,8 @@ public class Terminal { mClient = client; } - public void flushDamage() { - if (nativeFlushDamage(mNativePtr) != 0) { - throw new IllegalStateException("flushDamage failed"); - } - } - - public void resize(int rows, int cols) { - if (nativeResize(mNativePtr, rows, cols) != 0) { + public void resize(int rows, int cols, int scrollRows) { + if (nativeResize(mNativePtr, rows, cols, scrollRows) != 0) { throw new IllegalStateException("resize failed"); } } @@ -140,6 +139,10 @@ public class Terminal { return nativeGetCols(mNativePtr); } + public int getScrollRows() { + return nativeGetScrollRows(mNativePtr); + } + public void getCellRun(int row, int col, CellRun run) { if (nativeGetCellRun(mNativePtr, row, col, run) != 0) { throw new IllegalStateException("getCell failed"); @@ -159,16 +162,15 @@ public class Terminal { return nativeDispatchCharacter(mNativePtr, modifiers, character); } - private static native int nativeInit(TerminalCallbacks callbacks, int rows, int cols); - private static native int nativeRun(int ptr); - private static native int nativeStop(int ptr); + private static native int nativeDestroy(int ptr); - private static native int nativeFlushDamage(int ptr); - private static native int nativeResize(int ptr, int rows, int cols); + private static native int nativeRun(int ptr); + private static native int nativeResize(int ptr, int rows, int cols, int scrollRows); private static native int nativeGetCellRun(int ptr, int row, int col, CellRun run); private static native int nativeGetRows(int ptr); private static native int nativeGetCols(int ptr); + private static native int nativeGetScrollRows(int ptr); private static native boolean nativeDispatchKey(int ptr, int modifiers, int key); private static native boolean nativeDispatchCharacter(int ptr, int modifiers, int character); diff --git a/src/com/android/terminal/TerminalActivity.java b/src/com/android/terminal/TerminalActivity.java index 99d2be6..dd94d74 100644 --- a/src/com/android/terminal/TerminalActivity.java +++ b/src/com/android/terminal/TerminalActivity.java @@ -16,6 +16,8 @@ package com.android.terminal; +import static com.android.terminal.Terminal.TAG; + import android.app.Activity; import android.content.ComponentName; import android.content.Context; @@ -23,10 +25,12 @@ import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; +import android.os.Parcelable; import android.support.v4.view.PagerAdapter; import android.support.v4.view.PagerTitleStrip; import android.support.v4.view.ViewPager; import android.util.Log; +import android.util.SparseArray; import android.view.Menu; import android.view.MenuItem; import android.view.View; @@ -37,7 +41,6 @@ import android.view.ViewGroup; * {@link TerminalService}. */ public class TerminalActivity extends Activity { - private static final String TAG = "Terminal"; private TerminalService mService; @@ -59,6 +62,7 @@ public class TerminalActivity extends Activity { // Bind UI to known terminals mTermAdapter.notifyDataSetChanged(); + invalidateOptionsMenu(); } @Override @@ -69,6 +73,9 @@ public class TerminalActivity extends Activity { }; private final PagerAdapter mTermAdapter = new PagerAdapter() { + private SparseArray<SparseArray<Parcelable>> + mSavedState = new SparseArray<SparseArray<Parcelable>>(); + @Override public int getCount() { if (mService != null) { @@ -80,9 +87,17 @@ public class TerminalActivity extends Activity { @Override public Object instantiateItem(ViewGroup container, int position) { - final Terminal term = mService.getTerminals().get(position); final TerminalView view = new TerminalView(container.getContext()); + view.setId(android.R.id.list); + + final Terminal term = mService.getTerminals().valueAt(position); view.setTerminal(term); + + final SparseArray<Parcelable> state = mSavedState.get(term.key); + if (state != null) { + view.restoreHierarchyState(state); + } + container.addView(view); return view; } @@ -90,13 +105,24 @@ public class TerminalActivity extends Activity { @Override public void destroyItem(ViewGroup container, int position, Object object) { final TerminalView view = (TerminalView) object; + + final int key = view.getTerminal().key; + SparseArray<Parcelable> state = mSavedState.get(key); + if (state == null) { + state = new SparseArray<Parcelable>(); + mSavedState.put(key, state); + } + view.saveHierarchyState(state); + view.setTerminal(null); container.removeView(view); } @Override public int getItemPosition(Object object) { - final int index = mService.getTerminals().indexOf(object); + final TerminalView view = (TerminalView) object; + final int key = view.getTerminal().key; + final int index = mService.getTerminals().indexOfKey(key); if (index == -1) { return POSITION_NONE; } else { @@ -111,7 +137,7 @@ public class TerminalActivity extends Activity { @Override public CharSequence getPageTitle(int position) { - return mService.getTerminals().get(position).getTitle(); + return mService.getTerminals().valueAt(position).getTitle(); } }; @@ -147,21 +173,29 @@ public class TerminalActivity extends Activity { } @Override + public boolean onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); + menu.findItem(R.id.menu_close_tab).setEnabled(mTermAdapter.getCount() > 0); + return true; + } + + @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_new_tab: { mService.createTerminal(); mTermAdapter.notifyDataSetChanged(); + invalidateOptionsMenu(); final int index = mService.getTerminals().size() - 1; mPager.setCurrentItem(index, true); return true; } case R.id.menu_close_tab: { final int index = mPager.getCurrentItem(); - final Terminal term = mService.getTerminals().get(index); - mService.destroyTerminal(term); - // TODO: ask adamp about buggy zero item behavior + final int key = mService.getTerminals().keyAt(index); + mService.destroyTerminal(key); mTermAdapter.notifyDataSetChanged(); + invalidateOptionsMenu(); return true; } } diff --git a/src/com/android/terminal/TerminalCallbacks.java b/src/com/android/terminal/TerminalCallbacks.java index b449075..fb5a1ca 100644 --- a/src/com/android/terminal/TerminalCallbacks.java +++ b/src/com/android/terminal/TerminalCallbacks.java @@ -21,10 +21,6 @@ public abstract class TerminalCallbacks { return 1; } - public int prescroll(int startRow, int endRow, int startCol, int endCol) { - return 1; - } - public int moveRect(int destStartRow, int destEndRow, int destStartCol, int destEndCol, int srcStartRow, int srcEndRow, int srcStartCol, int srcEndCol) { return 1; @@ -53,8 +49,4 @@ public abstract class TerminalCallbacks { public int bell() { return 1; } - - public int resize(int rows, int cols) { - return 1; - } } diff --git a/src/com/android/terminal/TerminalKeys.java b/src/com/android/terminal/TerminalKeys.java index 17cab5e..67dc231 100644 --- a/src/com/android/terminal/TerminalKeys.java +++ b/src/com/android/terminal/TerminalKeys.java @@ -21,7 +21,7 @@ import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.View; -public class TerminalKeys implements View.OnKeyListener { +public class TerminalKeys { private static final String TAG = "TerminalKeys"; private static final boolean DEBUG = true; // Taken from vterm_input.h diff --git a/src/com/android/terminal/TerminalLineView.java b/src/com/android/terminal/TerminalLineView.java new file mode 100644 index 0000000..fff1301 --- /dev/null +++ b/src/com/android/terminal/TerminalLineView.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2013 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 com.android.terminal; + +import static com.android.terminal.Terminal.TAG; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.util.Log; +import android.view.View; + +import com.android.terminal.TerminalView.TerminalMetrics; + +/** + * Rendered contents of a single line of a {@link Terminal} session. + */ +public class TerminalLineView extends View { + public int pos; + public int row; + public int cols; + + private final Terminal mTerm; + private final TerminalMetrics mMetrics; + + public TerminalLineView(Context context, Terminal term, TerminalMetrics metrics) { + super(context); + mTerm = term; + mMetrics = metrics; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + setMeasuredDimension(getDefaultSize(0, widthMeasureSpec), + getDefaultSize(mMetrics.charHeight, heightMeasureSpec)); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + if (mTerm == null) { + Log.w(TAG, "onDraw() without a terminal"); + canvas.drawColor(Color.MAGENTA); + return; + } + + final TerminalMetrics m = mMetrics; + + for (int col = 0; col < cols;) { + mTerm.getCellRun(row, col, m.run); + + m.bgPaint.setColor(m.run.bg); + m.textPaint.setColor(m.run.fg); + + final int x = col * m.charWidth; + final int xEnd = x + (m.run.colSize * m.charWidth); + + canvas.save(); + canvas.translate(x, 0); + canvas.clipRect(0, 0, m.run.colSize * m.charWidth, m.charHeight); + + canvas.drawPaint(m.bgPaint); + canvas.drawPosText(m.run.data, 0, m.run.dataSize, m.pos, m.textPaint); + + canvas.restore(); + + col += m.run.colSize; + } + } +} diff --git a/src/com/android/terminal/TerminalService.java b/src/com/android/terminal/TerminalService.java index ebbdcce..4399390 100644 --- a/src/com/android/terminal/TerminalService.java +++ b/src/com/android/terminal/TerminalService.java @@ -20,19 +20,14 @@ import android.app.Service; import android.content.Intent; import android.os.Binder; import android.os.IBinder; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; +import android.util.SparseArray; /** * Background service that keeps {@link Terminal} instances running and warm * when UI isn't present. */ public class TerminalService extends Service { - private static final String TAG = "Terminal"; - - private final ArrayList<Terminal> mTerminals = new ArrayList<Terminal>(); + private final SparseArray<Terminal> mTerminals = new SparseArray<Terminal>(); public class ServiceBinder extends Binder { public TerminalService getService() { @@ -45,28 +40,29 @@ public class TerminalService extends Service { return new ServiceBinder(); } - public List<Terminal> getTerminals() { - return Collections.unmodifiableList(mTerminals); + public SparseArray<Terminal> getTerminals() { + return mTerminals; } - public Terminal createTerminal() { + public int createTerminal() { // If our first terminal, start ourselves as long-lived service - if (mTerminals.isEmpty()) { + if (mTerminals.size() == 0) { startService(new Intent(this, TerminalService.class)); } final Terminal term = new Terminal(); term.start(); - mTerminals.add(term); - return term; + mTerminals.put(term.key, term); + return term.key; } - public void destroyTerminal(Terminal term) { - term.stop(); - mTerminals.remove(term); + public void destroyTerminal(int key) { + final Terminal term = mTerminals.get(key); + term.destroy(); + mTerminals.delete(key); // If our last terminal, tear down long-lived service - if (mTerminals.isEmpty()) { + if (mTerminals.size() == 0) { stopService(new Intent(this, TerminalService.class)); } } diff --git a/src/com/android/terminal/TerminalView.java b/src/com/android/terminal/TerminalView.java index 4764183..72d1191 100644 --- a/src/com/android/terminal/TerminalView.java +++ b/src/com/android/terminal/TerminalView.java @@ -16,20 +16,23 @@ package com.android.terminal; +import static com.android.terminal.Terminal.TAG; + import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.FontMetrics; -import android.graphics.Rect; import android.graphics.Typeface; -import android.os.SystemClock; +import android.os.Parcelable; +import android.util.AttributeSet; import android.util.Log; +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewGroup; import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; -import android.view.KeyEvent; -import android.view.View; +import android.widget.BaseAdapter; +import android.widget.ListView; import com.android.terminal.Terminal.CellRun; import com.android.terminal.Terminal.TerminalClient; @@ -37,219 +40,246 @@ import com.android.terminal.Terminal.TerminalClient; /** * Rendered contents of a {@link Terminal} session. */ -public class TerminalView extends View { - private static final String TAG = "Terminal"; +public class TerminalView extends ListView { private static final boolean LOGD = true; - private static final int MAX_RUN_LENGTH = 128; + private static final boolean SCROLL_ON_DAMAGE = false; + private static final boolean SCROLL_ON_INPUT = true; - private final Context mContext; + private Terminal mTerm; - private final Paint mBgPaint = new Paint(); - private final Paint mTextPaint = new Paint(); + private boolean mScrolled; - /** Run of cells used when drawing */ - private final CellRun mRun; - /** Screen coordinates to draw chars into */ - private final float[] mPos; + private int mRows; + private int mCols; + private int mScrollRows; - private Terminal mTerm; + private final TerminalMetrics mMetrics = new TerminalMetrics(); + private final TerminalKeys mTermKeys = new TerminalKeys(); - private TerminalKeys mTermKeys; + /** + * Metrics shared between all {@link TerminalLineView} children. Locking + * provided by main thread. + */ + static class TerminalMetrics { + private static final int MAX_RUN_LENGTH = 128; - private int mCharTop; - private int mCharWidth; - private int mCharHeight; + final Paint bgPaint = new Paint(); + final Paint textPaint = new Paint(); - // TODO: for atomicity we might need to snapshot runs when processing - // callbacks driven by vterm thread + /** Run of cells used when drawing */ + final CellRun run; + /** Screen coordinates to draw chars into */ + final float[] pos; - private TerminalClient mClient = new TerminalClient() { - @Override - public void damage(int startRow, int endRow, int startCol, int endCol) { - if (LOGD) Log.d(TAG, "damage(" + startRow + ", " + endRow + ", " + startCol + ", " + endCol + ")"); - - // Invalidate region on screen - final int top = startRow * mCharHeight; - final int bottom = (endRow + 1) * mCharHeight; - final int left = startCol * mCharWidth; - final int right = (endCol + 1) * mCharWidth; - postInvalidate(left, top, right, bottom); + int charTop; + int charWidth; + int charHeight; + + public TerminalMetrics() { + run = new Terminal.CellRun(); + run.data = new char[MAX_RUN_LENGTH]; + + // Positions of each possible cell + // TODO: make sure this works with surrogate pairs + pos = new float[MAX_RUN_LENGTH * 2]; + setTextSize(20); } - @Override - public void moveRect(int destStartRow, int destEndRow, int destStartCol, int destEndCol, - int srcStartRow, int srcEndRow, int srcStartCol, int srcEndCol) { - // Treat as normal damage and perform full redraw - final int startRow = Math.min(destStartRow, srcStartRow); - final int endRow = Math.max(destEndRow, srcEndRow); - final int startCol = Math.min(destStartCol, srcStartCol); - final int endCol = Math.max(destEndCol, srcEndCol); - damage(startRow, endRow, startCol, endCol); + public void setTextSize(float textSize) { + textPaint.setTypeface(Typeface.MONOSPACE); + textPaint.setAntiAlias(true); + textPaint.setTextSize(textSize); + + // Read metrics to get exact pixel dimensions + final FontMetrics fm = textPaint.getFontMetrics(); + charTop = (int) Math.ceil(fm.top); + + final float[] widths = new float[1]; + textPaint.getTextWidths("X", widths); + charWidth = (int) Math.ceil(widths[0]); + charHeight = (int) Math.ceil(fm.descent - fm.top); + + // Update drawing positions + for (int i = 0; i < MAX_RUN_LENGTH; i++) { + pos[i * 2] = i * charWidth; + pos[(i * 2) + 1] = -charTop; + } } + } + private final Runnable mDamageRunnable = new Runnable() { @Override - public void bell() { - Log.i(TAG, "DING!"); + public void run() { + invalidateViews(); + if (SCROLL_ON_DAMAGE) { + scrollToBottom(true); + } } }; public TerminalView(Context context) { - super(context); - mContext = context; + this(context, null); + } - mRun = new Terminal.CellRun(); - mRun.data = new char[MAX_RUN_LENGTH]; + public TerminalView(Context context, AttributeSet attrs) { + this(context, attrs, com.android.internal.R.attr.listViewStyle); + } - // Positions of each possible cell - // TODO: make sure this works with surrogate pairs - mPos = new float[MAX_RUN_LENGTH * 2]; + public TerminalView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); - setBackgroundColor(Color.BLACK); - setTextSize(20); + setBackground(null); + setDivider(null); - // TODO: remove this test code that triggers invalidates - setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - v.invalidate(); - v.requestFocus(); - } - }); - - // Set view properties setFocusable(true); setFocusableInTouchMode(true); - setScrollContainer(true); - mTermKeys = new TerminalKeys(); - setOnKeyListener(mTermKeys); + setAdapter(mAdapter); + setOnKeyListener(mKeyListener); } - public void setTerminal(Terminal term) { - final Terminal orig = mTerm; - if (orig != null) { - orig.setClient(null); - } - mTerm = term; - if (term != null) { - term.setClient(mClient); - mTermKeys.setTerminal(term); + private final BaseAdapter mAdapter = new BaseAdapter() { + @Override + public View getView(int position, View convertView, ViewGroup parent) { + final TerminalLineView view; + if (convertView != null) { + view = (TerminalLineView) convertView; + } else { + view = new TerminalLineView(parent.getContext(), mTerm, mMetrics); + } + + view.pos = position; + view.row = posToRow(position); + view.cols = mCols; + return view; } - updateTerminalSize(); - } - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - if (mTerm != null) { - mTerm.setClient(mClient); + @Override + public long getItemId(int position) { + return position; } - } - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - if (mTerm != null) { - mTerm.setClient(null); + @Override + public Object getItem(int position) { + return null; } - } - public void setTextSize(float textSize) { - mTextPaint.setTypeface(Typeface.MONOSPACE); - mTextPaint.setAntiAlias(true); - mTextPaint.setTextSize(textSize); - - // Read metrics to get exact pixel dimensions - final FontMetrics fm = mTextPaint.getFontMetrics(); - mCharTop = (int) Math.ceil(fm.top); - - final float[] widths = new float[1]; - mTextPaint.getTextWidths("X", widths); - mCharWidth = (int) Math.ceil(widths[0]); - mCharHeight = (int) Math.ceil(fm.descent - fm.top); - - // Update drawing positions - for (int i = 0; i < MAX_RUN_LENGTH; i++) { - mPos[i * 2] = i * mCharWidth; - mPos[(i * 2) + 1] = -mCharTop; + @Override + public int getCount() { + if (mTerm != null) { + return mRows + mScrollRows; + } else { + return 0; + } } + }; - updateTerminalSize(); - } + private TerminalClient mClient = new TerminalClient() { + @Override + public void onDamage(final int startRow, final int endRow, int startCol, int endCol) { + post(mDamageRunnable); + } - /** - * Determine terminal dimensions based on current dimensions and font size, - * and request that {@link Terminal} change to that size. - */ - public void updateTerminalSize() { - if (getWidth() > 0 && getHeight() > 0 && mTerm != null) { - final int rows = getHeight() / mCharHeight; - final int cols = getWidth() / mCharWidth; - mTerm.resize(rows, cols); + @Override + public void onMoveRect(int destStartRow, int destEndRow, int destStartCol, int destEndCol, + int srcStartRow, int srcEndRow, int srcStartCol, int srcEndCol) { + post(mDamageRunnable); } - } - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - if (changed) { - updateTerminalSize(); + @Override + public void onBell() { + Log.i(TAG, "DING!"); } + }; + + private int rowToPos(int row) { + return row + mScrollRows; } - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); + private int posToRow(int pos) { + return pos - mScrollRows; + } - if (mTerm == null) { - Log.w(TAG, "onDraw() without a terminal"); - canvas.drawColor(Color.MAGENTA); - return; + private View.OnKeyListener mKeyListener = new OnKeyListener() { + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + final boolean res = mTermKeys.onKey(v, keyCode, event); + if (res && SCROLL_ON_INPUT) { + scrollToBottom(true); + } + return res; } + }; - final long start = SystemClock.elapsedRealtime(); + @Override + public void onRestoreInstanceState(Parcelable state) { + super.onRestoreInstanceState(state); + mScrolled = true; + } - // Only draw dirty region of console - final Rect dirty = canvas.getClipBounds(); + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + if (!mScrolled) { + scrollToBottom(false); + } + } - final int rows = mTerm.getRows(); - final int cols = mTerm.getCols(); + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); - final int startRow = dirty.top / mCharHeight; - final int endRow = Math.min(dirty.bottom / mCharHeight, rows - 1); - final int startCol = dirty.left / mCharWidth; - final int endCol = Math.min(dirty.right / mCharWidth, cols - 1); + final int rows = h / mMetrics.charHeight; + final int cols = w / mMetrics.charWidth; + final int scrollRows = mScrollRows; - final CellRun run = mRun; - final float[] pos = mPos; + final boolean sizeChanged = (rows != mRows || cols != mCols || scrollRows != mScrollRows); + if (mTerm != null && sizeChanged) { + mTerm.resize(rows, cols, scrollRows); - for (int row = startRow; row <= endRow; row++) { - for (int col = startCol; col <= endCol;) { - mTerm.getCellRun(row, col, run); + mRows = rows; + mCols = cols; + mScrollRows = scrollRows; - mBgPaint.setColor(run.bg); - mTextPaint.setColor(run.fg); + mAdapter.notifyDataSetChanged(); + } + } - final int y = row * mCharHeight; - final int x = col * mCharWidth; - final int xEnd = x + (run.colSize * mCharWidth); + public void scrollToBottom(boolean animate) { + final int dur = animate ? 250 : 0; + smoothScrollToPositionFromTop(getCount(), 0, dur); + mScrolled = true; + } - canvas.save(Canvas.MATRIX_SAVE_FLAG | Canvas.CLIP_SAVE_FLAG); - canvas.translate(x, y); - canvas.clipRect(0, 0, run.colSize * mCharWidth, mCharHeight); + public void setTerminal(Terminal term) { + final Terminal orig = mTerm; + if (orig != null) { + orig.setClient(null); + } + mTerm = term; + mScrolled = false; + if (term != null) { + term.setClient(mClient); + mTermKeys.setTerminal(term); - canvas.drawPaint(mBgPaint); - canvas.drawPosText(run.data, 0, run.dataSize, pos, mTextPaint); + // Populate any current settings + mRows = mTerm.getRows(); + mCols = mTerm.getCols(); + mScrollRows = mTerm.getScrollRows(); + mAdapter.notifyDataSetChanged(); + } + } - canvas.restore(); + public Terminal getTerminal() { + return mTerm; + } - col += run.colSize; - } - } + public void setTextSize(float textSize) { + mMetrics.setTextSize(textSize); - final long delta = SystemClock.elapsedRealtime() - start; - if (LOGD) Log.d(TAG, "onDraw() took " + delta + "ms"); + // Layout will kick off terminal resize when needed + requestLayout(); } @Override |