summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJeff Sharkey <jsharkey@android.com>2013-04-14 16:16:49 -0700
committerJeff Sharkey <jsharkey@android.com>2013-04-25 09:59:52 -0700
commit00b00812cd0c883c2380065d7fda29512d5477f0 (patch)
treee46d54ccffbcae9ca81736263e00c6381d7c8207
parenta816285f9c02e687268e76f02896ada33398b0dd (diff)
downloadandroid_packages_apps_Terminal-00b00812cd0c883c2380065d7fda29512d5477f0.tar.gz
android_packages_apps_Terminal-00b00812cd0c883c2380065d7fda29512d5477f0.tar.bz2
android_packages_apps_Terminal-00b00812cd0c883c2380065d7fda29512d5477f0.zip
Add scrollback support.
Switch terminal rendering to use ListView, splitting each row into a TerminalLineView item. This leverages existing ListView display list optimizations when scrolling, and gives us fling and overscroll for free. However, the simple case of a single line scrolling requires an entire screen rebind. Added locking between I/O thread and UI thread to provide consistent view of terminal state. Snap to current upstream libvterm, which has updated scrollback API. Examine full cell style when building runs. Address terminals using "keys" instead of indicies, since ordering can shift. Save and restore instance state to remember scrollback position. Avoid crashing after closing last terminal. Remove unused callbacks. Bug: 8332387 Change-Id: I06468d16ae8e1ff8ac79b7115c7cb3f9434b3c0d
-rw-r--r--AndroidManifest.xml5
-rw-r--r--jni/Android.mk9
-rw-r--r--jni/com_android_terminal_Terminal.cpp404
-rw-r--r--res/layout/activity.xml3
-rw-r--r--src/com/android/terminal/Terminal.java56
-rw-r--r--src/com/android/terminal/TerminalActivity.java48
-rw-r--r--src/com/android/terminal/TerminalCallbacks.java8
-rw-r--r--src/com/android/terminal/TerminalKeys.java2
-rw-r--r--src/com/android/terminal/TerminalLineView.java85
-rw-r--r--src/com/android/terminal/TerminalService.java30
-rw-r--r--src/com/android/terminal/TerminalView.java354
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