summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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