720 lines
21 KiB
C++
720 lines
21 KiB
C++
/*
|
|
* 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.
|
|
*/
|
|
|
|
#define LOG_TAG "Terminal"
|
|
|
|
#include <utils/Log.h>
|
|
#include <utils/Mutex.h>
|
|
#include "android_runtime/AndroidRuntime.h"
|
|
|
|
#include "jni.h"
|
|
#include <nativehelper/JNIHelp.h>
|
|
#include <nativehelper/ScopedLocalRef.h>
|
|
#include <nativehelper/ScopedPrimitiveArray.h>
|
|
|
|
#include <fcntl.h>
|
|
#include <pty.h>
|
|
#include <cstdio>
|
|
#include <termios.h>
|
|
#include <unistd.h>
|
|
|
|
#include <vterm.h>
|
|
|
|
#include <cstring>
|
|
|
|
#define USE_TEST_SHELL 0
|
|
#define DEBUG_CALLBACKS 0
|
|
#define DEBUG_IO 0
|
|
#define DEBUG_SCROLLBACK 0
|
|
|
|
namespace android {
|
|
|
|
/*
|
|
* Callback class reference
|
|
*/
|
|
static jclass terminalCallbacksClass;
|
|
|
|
/*
|
|
* Callback methods
|
|
*/
|
|
static jmethodID damageMethod;
|
|
static jmethodID moveRectMethod;
|
|
static jmethodID moveCursorMethod;
|
|
static jmethodID setTermPropBooleanMethod;
|
|
static jmethodID setTermPropIntMethod;
|
|
static jmethodID setTermPropStringMethod;
|
|
static jmethodID setTermPropColorMethod;
|
|
static jmethodID bellMethod;
|
|
|
|
/*
|
|
* CellRun class
|
|
*/
|
|
static jclass cellRunClass;
|
|
static jfieldID cellRunDataField;
|
|
static jfieldID cellRunDataSizeField;
|
|
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);
|
|
~Terminal();
|
|
|
|
status_t run();
|
|
|
|
size_t write(const char *bytes, size_t len);
|
|
|
|
bool dispatchCharacter(int mod, int character);
|
|
bool dispatchKey(int mod, int key);
|
|
bool flushInput();
|
|
|
|
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);
|
|
|
|
void getCellLocked(VTermPos pos, VTermScreenCell* cell);
|
|
|
|
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;
|
|
|
|
dimen_t mRows;
|
|
dimen_t mCols;
|
|
bool mKilled;
|
|
|
|
ScrollbackLine **mScroll;
|
|
dimen_t mScrollCur;
|
|
dimen_t mScrollSize;
|
|
|
|
};
|
|
|
|
/*
|
|
* VTerm event handlers
|
|
*/
|
|
|
|
static int term_damage(VTermRect rect, void *user) {
|
|
Terminal* term = reinterpret_cast<Terminal*>(user);
|
|
#if DEBUG_CALLBACKS
|
|
ALOGW("term_damage");
|
|
#endif
|
|
|
|
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_moverect(VTermRect dest, VTermRect src, void *user) {
|
|
Terminal* term = reinterpret_cast<Terminal*>(user);
|
|
#if DEBUG_CALLBACKS
|
|
ALOGW("term_moverect");
|
|
#endif
|
|
|
|
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);
|
|
}
|
|
|
|
static int term_movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user) {
|
|
Terminal* term = reinterpret_cast<Terminal*>(user);
|
|
#if DEBUG_CALLBACKS
|
|
ALOGW("term_movecursor");
|
|
#endif
|
|
|
|
JNIEnv* env = AndroidRuntime::getJNIEnv();
|
|
return env->CallIntMethod(term->getCallbacks(), moveCursorMethod, pos.row,
|
|
pos.col, oldpos.row, oldpos.col, visible);
|
|
}
|
|
|
|
static int term_settermprop(VTermProp prop, VTermValue *val, void *user) {
|
|
Terminal* term = reinterpret_cast<Terminal*>(user);
|
|
#if DEBUG_CALLBACKS
|
|
ALOGW("term_settermprop");
|
|
#endif
|
|
|
|
JNIEnv* env = AndroidRuntime::getJNIEnv();
|
|
switch (vterm_get_prop_type(prop)) {
|
|
case VTERM_VALUETYPE_BOOL:
|
|
return env->CallIntMethod(term->getCallbacks(), setTermPropBooleanMethod, prop,
|
|
val->boolean ? JNI_TRUE : JNI_FALSE);
|
|
case VTERM_VALUETYPE_INT:
|
|
return env->CallIntMethod(term->getCallbacks(), setTermPropIntMethod, prop, val->number);
|
|
case VTERM_VALUETYPE_STRING:
|
|
return env->CallIntMethod(term->getCallbacks(), setTermPropStringMethod, prop,
|
|
env->NewStringUTF(val->string));
|
|
case VTERM_VALUETYPE_COLOR:
|
|
return env->CallIntMethod(term->getCallbacks(), setTermPropIntMethod, prop,
|
|
val->color.rgb.red, val->color.rgb.green, val->color.rgb.blue);
|
|
default:
|
|
ALOGE("unknown callback type");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static int term_bell(void *user) {
|
|
Terminal* term = reinterpret_cast<Terminal*>(user);
|
|
#if DEBUG_CALLBACKS
|
|
ALOGW("term_bell");
|
|
#endif
|
|
|
|
JNIEnv* env = AndroidRuntime::getJNIEnv();
|
|
return env->CallIntMethod(term->getCallbacks(), bellMethod);
|
|
}
|
|
|
|
static int term_sb_pushline(int cols, const VTermScreenCell *cells, void *user) {
|
|
Terminal* term = reinterpret_cast<Terminal*>(user);
|
|
#if DEBUG_CALLBACKS
|
|
ALOGW("term_sb_pushline");
|
|
#endif
|
|
|
|
return term->onPushline(cols, cells);
|
|
}
|
|
|
|
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,
|
|
.moverect = term_moverect,
|
|
.movecursor = term_movecursor,
|
|
.settermprop = term_settermprop,
|
|
.bell = term_bell,
|
|
// 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) :
|
|
mCallbacks(callbacks), mRows(25), mCols(80), 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(mRows, mCols);
|
|
vterm_set_utf8(mVt, 1);
|
|
|
|
/* Set up screen */
|
|
mVts = vterm_obtain_screen(mVt);
|
|
vterm_screen_enable_altscreen(mVts, 1);
|
|
vterm_screen_set_callbacks(mVts, &cb, this);
|
|
vterm_screen_set_damage_merge(mVts, VTERM_DAMAGE_SCROLL);
|
|
vterm_screen_reset(mVts, 1);
|
|
}
|
|
|
|
Terminal::~Terminal() {
|
|
close(mMasterFd);
|
|
::kill(mChildPid, SIGHUP);
|
|
|
|
vterm_free(mVt);
|
|
|
|
delete mScroll;
|
|
|
|
JNIEnv *env = AndroidRuntime::getJNIEnv();
|
|
env->DeleteGlobalRef(mCallbacks);
|
|
}
|
|
|
|
status_t Terminal::run() {
|
|
struct termios termios;
|
|
memset(&termios, 0, sizeof(termios));
|
|
termios.c_iflag = ICRNL|IXON|IUTF8;
|
|
termios.c_oflag = OPOST|ONLCR|NL0|CR0|TAB0|BS0|VT0|FF0;
|
|
termios.c_cflag = CS8|CREAD;
|
|
termios.c_lflag = ISIG|ICANON|IEXTEN|ECHO|ECHOE|ECHOK;
|
|
|
|
cfsetispeed(&termios, B38400);
|
|
cfsetospeed(&termios, B38400);
|
|
|
|
termios.c_cc[VINTR] = 0x1f & 'C';
|
|
termios.c_cc[VQUIT] = 0x1f & '\\';
|
|
termios.c_cc[VERASE] = 0x7f;
|
|
termios.c_cc[VKILL] = 0x1f & 'U';
|
|
termios.c_cc[VEOF] = 0x1f & 'D';
|
|
termios.c_cc[VSTART] = 0x1f & 'Q';
|
|
termios.c_cc[VSTOP] = 0x1f & 'S';
|
|
termios.c_cc[VSUSP] = 0x1f & 'Z';
|
|
termios.c_cc[VREPRINT] = 0x1f & 'R';
|
|
termios.c_cc[VWERASE] = 0x1f & 'W';
|
|
termios.c_cc[VLNEXT] = 0x1f & 'V';
|
|
termios.c_cc[VMIN] = 1;
|
|
termios.c_cc[VTIME] = 0;
|
|
|
|
struct winsize size = { mRows, mCols, 0, 0 };
|
|
|
|
int stderr_save_fd = dup(2);
|
|
if (stderr_save_fd < 0) {
|
|
ALOGE("failed to dup stderr - %s", strerror(errno));
|
|
}
|
|
|
|
mChildPid = forkpty(&mMasterFd, NULL, &termios, &size);
|
|
if (mChildPid == 0) {
|
|
/* Restore the ISIG signals back to defaults */
|
|
signal(SIGINT, SIG_DFL);
|
|
signal(SIGQUIT, SIG_DFL);
|
|
signal(SIGSTOP, SIG_DFL);
|
|
signal(SIGCONT, SIG_DFL);
|
|
|
|
FILE *stderr_save = fdopen(stderr_save_fd, "a");
|
|
|
|
if (!stderr_save) {
|
|
ALOGE("failed to open stderr - %s", strerror(errno));
|
|
}
|
|
|
|
// We know execvp(2) won't actually try to modify this.
|
|
char *shell = const_cast<char*>("/system/bin/sh");
|
|
#if USE_TEST_SHELL
|
|
char *arg1 = const_cast<char*>("-c");
|
|
char *arg2 = const_cast<char*>("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");
|
|
char *args[4] = {shell, arg1, arg2, NULL};
|
|
#else
|
|
char *args[2] = {shell, NULL};
|
|
#endif
|
|
|
|
execvp(shell, args);
|
|
fprintf(stderr_save, "Cannot exec(%s) - %s\n", shell, strerror(errno));
|
|
_exit(1);
|
|
}
|
|
|
|
ALOGD("entering read() loop");
|
|
while (1) {
|
|
char buffer[4096];
|
|
ssize_t bytes = ::read(mMasterFd, buffer, sizeof buffer);
|
|
#if DEBUG_IO
|
|
ALOGD("read() returned %d bytes", bytes);
|
|
#endif
|
|
|
|
if (mKilled) {
|
|
ALOGD("kill() requested");
|
|
break;
|
|
}
|
|
if (bytes == 0) {
|
|
ALOGD("read() found EOF");
|
|
break;
|
|
}
|
|
if (bytes == -1) {
|
|
ALOGE("read() failed: %s", strerror(errno));
|
|
return 1;
|
|
}
|
|
|
|
{
|
|
Mutex::Autolock lock(mLock);
|
|
vterm_input_write(mVt, buffer, bytes);
|
|
vterm_screen_flush_damage(mVts);
|
|
}
|
|
}
|
|
|
|
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_keyboard_unichar(mVt, character, static_cast<VTermModifier>(mod));
|
|
return flushInput();
|
|
}
|
|
|
|
bool Terminal::dispatchKey(int mod, int key) {
|
|
Mutex::Autolock lock(mLock);
|
|
vterm_keyboard_key(mVt, static_cast<VTermKey>(key), static_cast<VTermModifier>(mod));
|
|
return flushInput();
|
|
}
|
|
|
|
bool Terminal::flushInput() {
|
|
size_t len = vterm_output_get_buffer_current(mVt);
|
|
if (len) {
|
|
char buf[len];
|
|
len = vterm_output_read(mVt, buf, len);
|
|
return len == write(buf, len);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
status_t Terminal::resize(dimen_t rows, dimen_t cols, dimen_t scrollRows) {
|
|
Mutex::Autolock lock(mLock);
|
|
|
|
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);
|
|
|
|
vterm_set_size(mVt, rows, cols);
|
|
vterm_screen_flush_damage(mVts);
|
|
|
|
return 0;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
dimen_t Terminal::getCols() const {
|
|
return mCols;
|
|
}
|
|
|
|
dimen_t Terminal::getScrollRows() const {
|
|
return mScrollSize;
|
|
}
|
|
|
|
jobject Terminal::getCallbacks() const {
|
|
return mCallbacks;
|
|
}
|
|
|
|
/*
|
|
* JNI glue
|
|
*/
|
|
|
|
static jlong com_android_terminal_Terminal_nativeInit(JNIEnv* env, jclass clazz, jobject callbacks) {
|
|
return reinterpret_cast<jlong>(new Terminal(callbacks));
|
|
}
|
|
|
|
static jint com_android_terminal_Terminal_nativeDestroy(JNIEnv* env, jclass clazz, jlong ptr) {
|
|
Terminal* term = reinterpret_cast<Terminal*>(ptr);
|
|
delete term;
|
|
return 0;
|
|
}
|
|
|
|
static jint com_android_terminal_Terminal_nativeRun(JNIEnv* env, jclass clazz, jlong ptr) {
|
|
Terminal* term = reinterpret_cast<Terminal*>(ptr);
|
|
return term->run();
|
|
}
|
|
|
|
static jint com_android_terminal_Terminal_nativeResize(JNIEnv* env,
|
|
jclass clazz, jlong ptr, jint rows, jint cols, jint scrollRows) {
|
|
Terminal* term = reinterpret_cast<Terminal*>(ptr);
|
|
return term->resize(rows, cols, scrollRows);
|
|
}
|
|
|
|
static inline int toArgb(const VTermColor& color) {
|
|
return (0xff << 24 | color.rgb.red << 16 | color.rgb.green << 8 | color.rgb.blue);
|
|
}
|
|
|
|
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, jlong 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);
|
|
if (data.get() == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
VTermScreenCell firstCell, cell;
|
|
|
|
VTermPos pos = {
|
|
.row = row,
|
|
.col = col,
|
|
};
|
|
|
|
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));
|
|
memcpy(&firstCell, &cell, sizeof(VTermScreenCell));
|
|
} else {
|
|
if (!isCellStyleEqual(cell, firstCell)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Only include cell chars if they fit into run
|
|
uint32_t rawCell = cell.chars[0];
|
|
size_t size = (rawCell < 0x10000) ? 1 : 2;
|
|
if (dataSize + size <= data.size()) {
|
|
if (rawCell < 0x10000) {
|
|
data[dataSize++] = rawCell;
|
|
} else {
|
|
data[dataSize++] = (((rawCell - 0x10000) >> 10) & 0x3ff) + 0xd800;
|
|
data[dataSize++] = ((rawCell - 0x10000) & 0x3ff) + 0xdc00;
|
|
}
|
|
|
|
for (int i = 1; i < cell.width; i++) {
|
|
data[dataSize++] = ' ';
|
|
}
|
|
|
|
colSize += cell.width;
|
|
pos.col += cell.width;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
env->SetIntField(run, cellRunDataSizeField, dataSize);
|
|
env->SetIntField(run, cellRunColSizeField, colSize);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static jint com_android_terminal_Terminal_nativeGetRows(JNIEnv* env, jclass clazz, jlong ptr) {
|
|
Terminal* term = reinterpret_cast<Terminal*>(ptr);
|
|
return term->getRows();
|
|
}
|
|
|
|
static jint com_android_terminal_Terminal_nativeGetCols(JNIEnv* env, jclass clazz, jlong ptr) {
|
|
Terminal* term = reinterpret_cast<Terminal*>(ptr);
|
|
return term->getCols();
|
|
}
|
|
|
|
static jint com_android_terminal_Terminal_nativeGetScrollRows(JNIEnv* env, jclass clazz, jlong ptr) {
|
|
Terminal* term = reinterpret_cast<Terminal*>(ptr);
|
|
return term->getScrollRows();
|
|
}
|
|
|
|
static jboolean com_android_terminal_Terminal_nativeDispatchCharacter(JNIEnv *env, jclass clazz,
|
|
jlong ptr, jint mod, jint c) {
|
|
Terminal* term = reinterpret_cast<Terminal*>(ptr);
|
|
return term->dispatchCharacter(mod, c);
|
|
}
|
|
|
|
static jboolean com_android_terminal_Terminal_nativeDispatchKey(JNIEnv *env, jclass clazz,
|
|
jlong ptr, jint mod, jint c) {
|
|
Terminal* term = reinterpret_cast<Terminal*>(ptr);
|
|
return term->dispatchKey(mod, c);
|
|
}
|
|
|
|
static JNINativeMethod gMethods[] = {
|
|
{ "nativeInit", "(Lcom/android/terminal/TerminalCallbacks;)J", (void*)com_android_terminal_Terminal_nativeInit },
|
|
{ "nativeDestroy", "(J)I", (void*)com_android_terminal_Terminal_nativeDestroy },
|
|
{ "nativeRun", "(J)I", (void*)com_android_terminal_Terminal_nativeRun },
|
|
{ "nativeResize", "(JIII)I", (void*)com_android_terminal_Terminal_nativeResize },
|
|
{ "nativeGetCellRun", "(JIILcom/android/terminal/Terminal$CellRun;)I", (void*)com_android_terminal_Terminal_nativeGetCellRun },
|
|
{ "nativeGetRows", "(J)I", (void*)com_android_terminal_Terminal_nativeGetRows },
|
|
{ "nativeGetCols", "(J)I", (void*)com_android_terminal_Terminal_nativeGetCols },
|
|
{ "nativeGetScrollRows", "(J)I", (void*)com_android_terminal_Terminal_nativeGetScrollRows },
|
|
{ "nativeDispatchCharacter", "(JII)Z", (void*)com_android_terminal_Terminal_nativeDispatchCharacter},
|
|
{ "nativeDispatchKey", "(JII)Z", (void*)com_android_terminal_Terminal_nativeDispatchKey },
|
|
};
|
|
|
|
int register_com_android_terminal_Terminal(JNIEnv* env) {
|
|
ScopedLocalRef<jclass> localClass(env,
|
|
env->FindClass("com/android/terminal/TerminalCallbacks"));
|
|
|
|
android::terminalCallbacksClass = reinterpret_cast<jclass>(env->NewGlobalRef(localClass.get()));
|
|
|
|
android::damageMethod = env->GetMethodID(terminalCallbacksClass, "damage", "(IIII)I");
|
|
android::moveRectMethod = env->GetMethodID(terminalCallbacksClass, "moveRect", "(IIIIIIII)I");
|
|
android::moveCursorMethod = env->GetMethodID(terminalCallbacksClass, "moveCursor",
|
|
"(IIIII)I");
|
|
android::setTermPropBooleanMethod = env->GetMethodID(terminalCallbacksClass,
|
|
"setTermPropBoolean", "(IZ)I");
|
|
android::setTermPropIntMethod = env->GetMethodID(terminalCallbacksClass, "setTermPropInt",
|
|
"(II)I");
|
|
android::setTermPropStringMethod = env->GetMethodID(terminalCallbacksClass, "setTermPropString",
|
|
"(ILjava/lang/String;)I");
|
|
android::setTermPropColorMethod = env->GetMethodID(terminalCallbacksClass, "setTermPropColor",
|
|
"(IIII)I");
|
|
android::bellMethod = env->GetMethodID(terminalCallbacksClass, "bell", "()I");
|
|
|
|
ScopedLocalRef<jclass> cellRunLocal(env,
|
|
env->FindClass("com/android/terminal/Terminal$CellRun"));
|
|
cellRunClass = reinterpret_cast<jclass>(env->NewGlobalRef(cellRunLocal.get()));
|
|
cellRunDataField = env->GetFieldID(cellRunClass, "data", "[C");
|
|
cellRunDataSizeField = env->GetFieldID(cellRunClass, "dataSize", "I");
|
|
cellRunColSizeField = env->GetFieldID(cellRunClass, "colSize", "I");
|
|
cellRunFgField = env->GetFieldID(cellRunClass, "fg", "I");
|
|
cellRunBgField = env->GetFieldID(cellRunClass, "bg", "I");
|
|
|
|
return jniRegisterNativeMethods(env, "com/android/terminal/Terminal",
|
|
gMethods, NELEM(gMethods));
|
|
}
|
|
|
|
} /* namespace android */
|