Home | History | Annotate | Download | only in jni
      1 /*
      2  * Copyright (C) 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 #define LOG_TAG "Terminal"
     18 
     19 #include <utils/Log.h>
     20 #include <utils/Mutex.h>
     21 #include "android_runtime/AndroidRuntime.h"
     22 
     23 #include "jni.h"
     24 #include <nativehelper/JNIHelp.h>
     25 #include <nativehelper/ScopedLocalRef.h>
     26 #include <nativehelper/ScopedPrimitiveArray.h>
     27 
     28 #include <fcntl.h>
     29 #include <pty.h>
     30 #include <stdio.h>
     31 #include <termios.h>
     32 #include <unistd.h>
     33 
     34 #include <vterm.h>
     35 
     36 #include <string.h>
     37 
     38 #define USE_TEST_SHELL 0
     39 #define DEBUG_CALLBACKS 0
     40 #define DEBUG_IO 0
     41 #define DEBUG_SCROLLBACK 0
     42 
     43 namespace android {
     44 
     45 /*
     46  * Callback class reference
     47  */
     48 static jclass terminalCallbacksClass;
     49 
     50 /*
     51  * Callback methods
     52  */
     53 static jmethodID damageMethod;
     54 static jmethodID moveRectMethod;
     55 static jmethodID moveCursorMethod;
     56 static jmethodID setTermPropBooleanMethod;
     57 static jmethodID setTermPropIntMethod;
     58 static jmethodID setTermPropStringMethod;
     59 static jmethodID setTermPropColorMethod;
     60 static jmethodID bellMethod;
     61 
     62 /*
     63  * CellRun class
     64  */
     65 static jclass cellRunClass;
     66 static jfieldID cellRunDataField;
     67 static jfieldID cellRunDataSizeField;
     68 static jfieldID cellRunColSizeField;
     69 static jfieldID cellRunFgField;
     70 static jfieldID cellRunBgField;
     71 
     72 typedef short unsigned int dimen_t;
     73 
     74 class ScrollbackLine {
     75 public:
     76     inline ScrollbackLine(dimen_t _cols) : cols(_cols) {
     77         mCells = new VTermScreenCell[cols];
     78     };
     79     inline ~ScrollbackLine() {
     80         delete mCells;
     81     }
     82 
     83     inline dimen_t copyFrom(dimen_t cols, const VTermScreenCell* cells) {
     84         dimen_t n = this->cols > cols ? cols : this->cols;
     85         memcpy(mCells, cells, sizeof(VTermScreenCell) * n);
     86         return n;
     87     }
     88 
     89     inline dimen_t copyTo(dimen_t cols, VTermScreenCell* cells) {
     90         dimen_t n = cols > this->cols ? this->cols : cols;
     91         memcpy(cells, mCells, sizeof(VTermScreenCell) * n);
     92         return n;
     93     }
     94 
     95     inline void getCell(dimen_t col, VTermScreenCell* cell) {
     96         *cell = mCells[col];
     97     }
     98 
     99     const dimen_t cols;
    100 
    101 private:
    102     VTermScreenCell* mCells;
    103 };
    104 
    105 /*
    106  * Terminal session
    107  */
    108 class Terminal {
    109 public:
    110     Terminal(jobject callbacks);
    111     ~Terminal();
    112 
    113     status_t run();
    114 
    115     size_t write(const char *bytes, size_t len);
    116 
    117     bool dispatchCharacter(int mod, int character);
    118     bool dispatchKey(int mod, int key);
    119     bool flushInput();
    120 
    121     status_t resize(dimen_t rows, dimen_t cols, dimen_t scrollRows);
    122 
    123     status_t onPushline(dimen_t cols, const VTermScreenCell* cells);
    124     status_t onPopline(dimen_t cols, VTermScreenCell* cells);
    125 
    126     void getCellLocked(VTermPos pos, VTermScreenCell* cell);
    127 
    128     dimen_t getRows() const;
    129     dimen_t getCols() const;
    130     dimen_t getScrollRows() const;
    131 
    132     jobject getCallbacks() const;
    133 
    134     // Lock protecting mutations of internal libvterm state
    135     Mutex mLock;
    136 
    137 private:
    138     int mMasterFd;
    139     pid_t mChildPid;
    140     VTerm *mVt;
    141     VTermScreen *mVts;
    142 
    143     jobject mCallbacks;
    144 
    145     dimen_t mRows;
    146     dimen_t mCols;
    147     bool mKilled;
    148 
    149     ScrollbackLine **mScroll;
    150     dimen_t mScrollCur;
    151     dimen_t mScrollSize;
    152 
    153 };
    154 
    155 /*
    156  * VTerm event handlers
    157  */
    158 
    159 static int term_damage(VTermRect rect, void *user) {
    160     Terminal* term = reinterpret_cast<Terminal*>(user);
    161 #if DEBUG_CALLBACKS
    162     ALOGW("term_damage");
    163 #endif
    164 
    165     JNIEnv* env = AndroidRuntime::getJNIEnv();
    166     return env->CallIntMethod(term->getCallbacks(), damageMethod, rect.start_row, rect.end_row,
    167             rect.start_col, rect.end_col);
    168 }
    169 
    170 static int term_moverect(VTermRect dest, VTermRect src, void *user) {
    171     Terminal* term = reinterpret_cast<Terminal*>(user);
    172 #if DEBUG_CALLBACKS
    173     ALOGW("term_moverect");
    174 #endif
    175 
    176     JNIEnv* env = AndroidRuntime::getJNIEnv();
    177     return env->CallIntMethod(term->getCallbacks(), moveRectMethod,
    178             dest.start_row, dest.end_row, dest.start_col, dest.end_col,
    179             src.start_row, src.end_row, src.start_col, src.end_col);
    180 }
    181 
    182 static int term_movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user) {
    183     Terminal* term = reinterpret_cast<Terminal*>(user);
    184 #if DEBUG_CALLBACKS
    185     ALOGW("term_movecursor");
    186 #endif
    187 
    188     JNIEnv* env = AndroidRuntime::getJNIEnv();
    189     return env->CallIntMethod(term->getCallbacks(), moveCursorMethod, pos.row,
    190             pos.col, oldpos.row, oldpos.col, visible);
    191 }
    192 
    193 static int term_settermprop(VTermProp prop, VTermValue *val, void *user) {
    194     Terminal* term = reinterpret_cast<Terminal*>(user);
    195 #if DEBUG_CALLBACKS
    196     ALOGW("term_settermprop");
    197 #endif
    198 
    199     JNIEnv* env = AndroidRuntime::getJNIEnv();
    200     switch (vterm_get_prop_type(prop)) {
    201     case VTERM_VALUETYPE_BOOL:
    202         return env->CallIntMethod(term->getCallbacks(), setTermPropBooleanMethod, prop,
    203                 val->boolean ? JNI_TRUE : JNI_FALSE);
    204     case VTERM_VALUETYPE_INT:
    205         return env->CallIntMethod(term->getCallbacks(), setTermPropIntMethod, prop, val->number);
    206     case VTERM_VALUETYPE_STRING:
    207         return env->CallIntMethod(term->getCallbacks(), setTermPropStringMethod, prop,
    208                 env->NewStringUTF(val->string));
    209     case VTERM_VALUETYPE_COLOR:
    210         return env->CallIntMethod(term->getCallbacks(), setTermPropIntMethod, prop, val->color.red,
    211                 val->color.green, val->color.blue);
    212     default:
    213         ALOGE("unknown callback type");
    214         return 0;
    215     }
    216 }
    217 
    218 static int term_setmousefunc(VTermMouseFunc func, void *data, void *user) {
    219     Terminal* term = reinterpret_cast<Terminal*>(user);
    220 #if DEBUG_CALLBACKS
    221     ALOGW("term_setmousefunc");
    222 #endif
    223     return 1;
    224 }
    225 
    226 static int term_bell(void *user) {
    227     Terminal* term = reinterpret_cast<Terminal*>(user);
    228 #if DEBUG_CALLBACKS
    229     ALOGW("term_bell");
    230 #endif
    231 
    232     JNIEnv* env = AndroidRuntime::getJNIEnv();
    233     return env->CallIntMethod(term->getCallbacks(), bellMethod);
    234 }
    235 
    236 static int term_sb_pushline(int cols, const VTermScreenCell *cells, void *user) {
    237     Terminal* term = reinterpret_cast<Terminal*>(user);
    238 #if DEBUG_CALLBACKS
    239     ALOGW("term_sb_pushline");
    240 #endif
    241 
    242     return term->onPushline(cols, cells);
    243 }
    244 
    245 static int term_sb_popline(int cols, VTermScreenCell *cells, void *user) {
    246     Terminal* term = reinterpret_cast<Terminal*>(user);
    247 #if DEBUG_CALLBACKS
    248     ALOGW("term_sb_popline");
    249 #endif
    250 
    251     return term->onPopline(cols, cells);
    252 }
    253 
    254 static VTermScreenCallbacks cb = {
    255     .damage = term_damage,
    256     .moverect = term_moverect,
    257     .movecursor = term_movecursor,
    258     .settermprop = term_settermprop,
    259     .setmousefunc = term_setmousefunc,
    260     .bell = term_bell,
    261     // Resize requests are applied immediately, so callback is ignored
    262     .resize = NULL,
    263     .sb_pushline = term_sb_pushline,
    264     .sb_popline = term_sb_popline,
    265 };
    266 
    267 Terminal::Terminal(jobject callbacks) :
    268         mCallbacks(callbacks), mRows(25), mCols(80), mKilled(false),
    269         mScrollCur(0), mScrollSize(100) {
    270     JNIEnv* env = AndroidRuntime::getJNIEnv();
    271     mCallbacks = env->NewGlobalRef(callbacks);
    272 
    273     mScroll = new ScrollbackLine*[mScrollSize];
    274     memset(mScroll, 0, sizeof(ScrollbackLine*) * mScrollSize);
    275 
    276     /* Create VTerm */
    277     mVt = vterm_new(mRows, mCols);
    278     vterm_parser_set_utf8(mVt, 1);
    279 
    280     /* Set up screen */
    281     mVts = vterm_obtain_screen(mVt);
    282     vterm_screen_enable_altscreen(mVts, 1);
    283     vterm_screen_set_callbacks(mVts, &cb, this);
    284     vterm_screen_set_damage_merge(mVts, VTERM_DAMAGE_SCROLL);
    285     vterm_screen_reset(mVts, 1);
    286 }
    287 
    288 Terminal::~Terminal() {
    289     close(mMasterFd);
    290     ::kill(mChildPid, SIGHUP);
    291 
    292     vterm_free(mVt);
    293 
    294     delete mScroll;
    295 
    296     JNIEnv *env = AndroidRuntime::getJNIEnv();
    297     env->DeleteGlobalRef(mCallbacks);
    298 }
    299 
    300 status_t Terminal::run() {
    301     struct termios termios;
    302     memset(&termios, 0, sizeof(termios));
    303     termios.c_iflag = ICRNL|IXON|IUTF8;
    304     termios.c_oflag = OPOST|ONLCR|NL0|CR0|TAB0|BS0|VT0|FF0;
    305     termios.c_cflag = CS8|CREAD;
    306     termios.c_lflag = ISIG|ICANON|IEXTEN|ECHO|ECHOE|ECHOK;
    307 
    308     cfsetispeed(&termios, B38400);
    309     cfsetospeed(&termios, B38400);
    310 
    311     termios.c_cc[VINTR]    = 0x1f & 'C';
    312     termios.c_cc[VQUIT]    = 0x1f & '\\';
    313     termios.c_cc[VERASE]   = 0x7f;
    314     termios.c_cc[VKILL]    = 0x1f & 'U';
    315     termios.c_cc[VEOF]     = 0x1f & 'D';
    316     termios.c_cc[VSTART]   = 0x1f & 'Q';
    317     termios.c_cc[VSTOP]    = 0x1f & 'S';
    318     termios.c_cc[VSUSP]    = 0x1f & 'Z';
    319     termios.c_cc[VREPRINT] = 0x1f & 'R';
    320     termios.c_cc[VWERASE]  = 0x1f & 'W';
    321     termios.c_cc[VLNEXT]   = 0x1f & 'V';
    322     termios.c_cc[VMIN]     = 1;
    323     termios.c_cc[VTIME]    = 0;
    324 
    325     struct winsize size = { mRows, mCols, 0, 0 };
    326 
    327     int stderr_save_fd = dup(2);
    328     if (stderr_save_fd < 0) {
    329         ALOGE("failed to dup stderr - %s", strerror(errno));
    330     }
    331 
    332     mChildPid = forkpty(&mMasterFd, NULL, &termios, &size);
    333     if (mChildPid == 0) {
    334         /* Restore the ISIG signals back to defaults */
    335         signal(SIGINT, SIG_DFL);
    336         signal(SIGQUIT, SIG_DFL);
    337         signal(SIGSTOP, SIG_DFL);
    338         signal(SIGCONT, SIG_DFL);
    339 
    340         FILE *stderr_save = fdopen(stderr_save_fd, "a");
    341 
    342         if (!stderr_save) {
    343             ALOGE("failed to open stderr - %s", strerror(errno));
    344         }
    345 
    346         // We know execvp(2) won't actually try to modify this.
    347         char *shell = const_cast<char*>("/system/bin/sh");
    348 #if USE_TEST_SHELL
    349         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};
    350 #else
    351         char *args[2] = {shell, NULL};
    352 #endif
    353 
    354         execvp(shell, args);
    355         fprintf(stderr_save, "Cannot exec(%s) - %s\n", shell, strerror(errno));
    356         _exit(1);
    357     }
    358 
    359     ALOGD("entering read() loop");
    360     while (1) {
    361         char buffer[4096];
    362         ssize_t bytes = ::read(mMasterFd, buffer, sizeof buffer);
    363 #if DEBUG_IO
    364         ALOGD("read() returned %d bytes", bytes);
    365 #endif
    366 
    367         if (mKilled) {
    368             ALOGD("kill() requested");
    369             break;
    370         }
    371         if (bytes == 0) {
    372             ALOGD("read() found EOF");
    373             break;
    374         }
    375         if (bytes == -1) {
    376             ALOGE("read() failed: %s", strerror(errno));
    377             return 1;
    378         }
    379 
    380         {
    381             Mutex::Autolock lock(mLock);
    382             vterm_push_bytes(mVt, buffer, bytes);
    383             vterm_screen_flush_damage(mVts);
    384         }
    385     }
    386 
    387     return 0;
    388 }
    389 
    390 size_t Terminal::write(const char *bytes, size_t len) {
    391     return ::write(mMasterFd, bytes, len);
    392 }
    393 
    394 bool Terminal::dispatchCharacter(int mod, int character) {
    395     Mutex::Autolock lock(mLock);
    396     vterm_input_push_char(mVt, static_cast<VTermModifier>(mod), character);
    397     return flushInput();
    398 }
    399 
    400 bool Terminal::dispatchKey(int mod, int key) {
    401     Mutex::Autolock lock(mLock);
    402     vterm_input_push_key(mVt, static_cast<VTermModifier>(mod), static_cast<VTermKey>(key));
    403     return flushInput();
    404 }
    405 
    406 bool Terminal::flushInput() {
    407     size_t len = vterm_output_get_buffer_current(mVt);
    408     if (len) {
    409         char buf[len];
    410         len = vterm_output_bufferread(mVt, buf, len);
    411         return len == write(buf, len);
    412     }
    413     return true;
    414 }
    415 
    416 status_t Terminal::resize(dimen_t rows, dimen_t cols, dimen_t scrollRows) {
    417     Mutex::Autolock lock(mLock);
    418 
    419     ALOGD("resize(%d, %d, %d)", rows, cols, scrollRows);
    420 
    421     mRows = rows;
    422     mCols = cols;
    423     // TODO: resize scrollback
    424 
    425     struct winsize size = { rows, cols, 0, 0 };
    426     ioctl(mMasterFd, TIOCSWINSZ, &size);
    427 
    428     vterm_set_size(mVt, rows, cols);
    429     vterm_screen_flush_damage(mVts);
    430 
    431     return 0;
    432 }
    433 
    434 status_t Terminal::onPushline(dimen_t cols, const VTermScreenCell* cells) {
    435     ScrollbackLine* line = NULL;
    436     if (mScrollCur == mScrollSize) {
    437         /* Recycle old row if it's the right size */
    438         if (mScroll[mScrollCur - 1]->cols == cols) {
    439             line = mScroll[mScrollCur - 1];
    440         } else {
    441             delete mScroll[mScrollCur - 1];
    442         }
    443 
    444         memmove(mScroll + 1, mScroll, sizeof(ScrollbackLine*) * (mScrollCur - 1));
    445     } else if (mScrollCur > 0) {
    446         memmove(mScroll + 1, mScroll, sizeof(ScrollbackLine*) * mScrollCur);
    447     }
    448 
    449     if (line == NULL) {
    450         line = new ScrollbackLine(cols);
    451     }
    452 
    453     mScroll[0] = line;
    454 
    455     if (mScrollCur < mScrollSize) {
    456         mScrollCur++;
    457     }
    458 
    459     line->copyFrom(cols, cells);
    460     return 1;
    461 }
    462 
    463 status_t Terminal::onPopline(dimen_t cols, VTermScreenCell* cells) {
    464     if (mScrollCur == 0) {
    465         return 0;
    466     }
    467 
    468     ScrollbackLine* line = mScroll[0];
    469     mScrollCur--;
    470     memmove(mScroll, mScroll + 1, sizeof(ScrollbackLine*) * mScrollCur);
    471 
    472     dimen_t n = line->copyTo(cols, cells);
    473     for (dimen_t col = n; col < cols; col++) {
    474         cells[col].chars[0] = 0;
    475         cells[col].width = 1;
    476     }
    477 
    478     delete line;
    479     return 1;
    480 }
    481 
    482 void Terminal::getCellLocked(VTermPos pos, VTermScreenCell* cell) {
    483     // The UI may be asking for cell data while the model is changing
    484     // underneath it, so we always fill with meaningful data.
    485 
    486     if (pos.row < 0) {
    487         size_t scrollRow = -pos.row;
    488         if (scrollRow > mScrollCur) {
    489             // Invalid region above current scrollback
    490             cell->width = 1;
    491 #if DEBUG_SCROLLBACK
    492             cell->bg.red = 255;
    493 #endif
    494             return;
    495         }
    496 
    497         ScrollbackLine* line = mScroll[scrollRow - 1];
    498         if ((size_t) pos.col < line->cols) {
    499             // Valid scrollback cell
    500             line->getCell(pos.col, cell);
    501             cell->width = 1;
    502 #if DEBUG_SCROLLBACK
    503             cell->bg.blue = 255;
    504 #endif
    505             return;
    506         } else {
    507             // Extend last scrollback cell into invalid region
    508             line->getCell(line->cols - 1, cell);
    509             cell->width = 1;
    510             cell->chars[0] = ' ';
    511 #if DEBUG_SCROLLBACK
    512             cell->bg.green = 255;
    513 #endif
    514             return;
    515         }
    516     }
    517 
    518     if ((size_t) pos.row >= mRows) {
    519         // Invalid region below screen
    520         cell->width = 1;
    521 #if DEBUG_SCROLLBACK
    522         cell->bg.red = 128;
    523 #endif
    524         return;
    525     }
    526 
    527     // Valid screen cell
    528     vterm_screen_get_cell(mVts, pos, cell);
    529 }
    530 
    531 dimen_t Terminal::getRows() const {
    532     return mRows;
    533 }
    534 
    535 dimen_t Terminal::getCols() const {
    536     return mCols;
    537 }
    538 
    539 dimen_t Terminal::getScrollRows() const {
    540     return mScrollSize;
    541 }
    542 
    543 jobject Terminal::getCallbacks() const {
    544     return mCallbacks;
    545 }
    546 
    547 /*
    548  * JNI glue
    549  */
    550 
    551 static jlong com_android_terminal_Terminal_nativeInit(JNIEnv* env, jclass clazz, jobject callbacks) {
    552     return reinterpret_cast<jlong>(new Terminal(callbacks));
    553 }
    554 
    555 static jint com_android_terminal_Terminal_nativeDestroy(JNIEnv* env, jclass clazz, jlong ptr) {
    556     Terminal* term = reinterpret_cast<Terminal*>(ptr);
    557     delete term;
    558     return 0;
    559 }
    560 
    561 static jint com_android_terminal_Terminal_nativeRun(JNIEnv* env, jclass clazz, jlong ptr) {
    562     Terminal* term = reinterpret_cast<Terminal*>(ptr);
    563     return term->run();
    564 }
    565 
    566 static jint com_android_terminal_Terminal_nativeResize(JNIEnv* env,
    567         jclass clazz, jlong ptr, jint rows, jint cols, jint scrollRows) {
    568     Terminal* term = reinterpret_cast<Terminal*>(ptr);
    569     return term->resize(rows, cols, scrollRows);
    570 }
    571 
    572 static inline int toArgb(const VTermColor& color) {
    573     return (0xff << 24 | color.red << 16 | color.green << 8 | color.blue);
    574 }
    575 
    576 static inline bool isCellStyleEqual(const VTermScreenCell& a, const VTermScreenCell& b) {
    577     if (toArgb(a.fg) != toArgb(b.fg)) return false;
    578     if (toArgb(a.bg) != toArgb(b.bg)) return false;
    579 
    580     if (a.attrs.bold != b.attrs.bold) return false;
    581     if (a.attrs.underline != b.attrs.underline) return false;
    582     if (a.attrs.italic != b.attrs.italic) return false;
    583     if (a.attrs.blink != b.attrs.blink) return false;
    584     if (a.attrs.reverse != b.attrs.reverse) return false;
    585     if (a.attrs.strike != b.attrs.strike) return false;
    586     if (a.attrs.font != b.attrs.font) return false;
    587 
    588     return true;
    589 }
    590 
    591 static jint com_android_terminal_Terminal_nativeGetCellRun(JNIEnv* env,
    592         jclass clazz, jlong ptr, jint row, jint col, jobject run) {
    593     Terminal* term = reinterpret_cast<Terminal*>(ptr);
    594     Mutex::Autolock lock(term->mLock);
    595 
    596     jcharArray dataArray = (jcharArray) env->GetObjectField(run, cellRunDataField);
    597     ScopedCharArrayRW data(env, dataArray);
    598     if (data.get() == NULL) {
    599         return -1;
    600     }
    601 
    602     VTermScreenCell firstCell, cell;
    603 
    604     VTermPos pos = {
    605         .row = row,
    606         .col = col,
    607     };
    608 
    609     size_t dataSize = 0;
    610     size_t colSize = 0;
    611     while ((size_t) pos.col < term->getCols()) {
    612         memset(&cell, 0, sizeof(VTermScreenCell));
    613         term->getCellLocked(pos, &cell);
    614 
    615         if (colSize == 0) {
    616             env->SetIntField(run, cellRunFgField, toArgb(cell.fg));
    617             env->SetIntField(run, cellRunBgField, toArgb(cell.bg));
    618             memcpy(&firstCell, &cell, sizeof(VTermScreenCell));
    619         } else {
    620             if (!isCellStyleEqual(cell, firstCell)) {
    621                 break;
    622             }
    623         }
    624 
    625         // Only include cell chars if they fit into run
    626         uint32_t rawCell = cell.chars[0];
    627         size_t size = (rawCell < 0x10000) ? 1 : 2;
    628         if (dataSize + size <= data.size()) {
    629             if (rawCell < 0x10000) {
    630                 data[dataSize++] = rawCell;
    631             } else {
    632                 data[dataSize++] = (((rawCell - 0x10000) >> 10) & 0x3ff) + 0xd800;
    633                 data[dataSize++] = ((rawCell - 0x10000) & 0x3ff) + 0xdc00;
    634             }
    635 
    636             for (int i = 1; i < cell.width; i++) {
    637                 data[dataSize++] = ' ';
    638             }
    639 
    640             colSize += cell.width;
    641             pos.col += cell.width;
    642         } else {
    643             break;
    644         }
    645     }
    646 
    647     env->SetIntField(run, cellRunDataSizeField, dataSize);
    648     env->SetIntField(run, cellRunColSizeField, colSize);
    649 
    650     return 0;
    651 }
    652 
    653 static jint com_android_terminal_Terminal_nativeGetRows(JNIEnv* env, jclass clazz, jlong ptr) {
    654     Terminal* term = reinterpret_cast<Terminal*>(ptr);
    655     return term->getRows();
    656 }
    657 
    658 static jint com_android_terminal_Terminal_nativeGetCols(JNIEnv* env, jclass clazz, jlong ptr) {
    659     Terminal* term = reinterpret_cast<Terminal*>(ptr);
    660     return term->getCols();
    661 }
    662 
    663 static jint com_android_terminal_Terminal_nativeGetScrollRows(JNIEnv* env, jclass clazz, jlong ptr) {
    664     Terminal* term = reinterpret_cast<Terminal*>(ptr);
    665     return term->getScrollRows();
    666 }
    667 
    668 static jboolean com_android_terminal_Terminal_nativeDispatchCharacter(JNIEnv *env, jclass clazz,
    669         jlong ptr, jint mod, jint c) {
    670     Terminal* term = reinterpret_cast<Terminal*>(ptr);
    671     return term->dispatchCharacter(mod, c);
    672 }
    673 
    674 static jboolean com_android_terminal_Terminal_nativeDispatchKey(JNIEnv *env, jclass clazz,
    675         jlong ptr, jint mod, jint c) {
    676     Terminal* term = reinterpret_cast<Terminal*>(ptr);
    677     return term->dispatchKey(mod, c);
    678 }
    679 
    680 static JNINativeMethod gMethods[] = {
    681     { "nativeInit", "(Lcom/android/terminal/TerminalCallbacks;)J", (void*)com_android_terminal_Terminal_nativeInit },
    682     { "nativeDestroy", "(J)I", (void*)com_android_terminal_Terminal_nativeDestroy },
    683     { "nativeRun", "(J)I", (void*)com_android_terminal_Terminal_nativeRun },
    684     { "nativeResize", "(JIII)I", (void*)com_android_terminal_Terminal_nativeResize },
    685     { "nativeGetCellRun", "(JIILcom/android/terminal/Terminal$CellRun;)I", (void*)com_android_terminal_Terminal_nativeGetCellRun },
    686     { "nativeGetRows", "(J)I", (void*)com_android_terminal_Terminal_nativeGetRows },
    687     { "nativeGetCols", "(J)I", (void*)com_android_terminal_Terminal_nativeGetCols },
    688     { "nativeGetScrollRows", "(J)I", (void*)com_android_terminal_Terminal_nativeGetScrollRows },
    689     { "nativeDispatchCharacter", "(JII)Z", (void*)com_android_terminal_Terminal_nativeDispatchCharacter},
    690     { "nativeDispatchKey", "(JII)Z", (void*)com_android_terminal_Terminal_nativeDispatchKey },
    691 };
    692 
    693 int register_com_android_terminal_Terminal(JNIEnv* env) {
    694     ScopedLocalRef<jclass> localClass(env,
    695             env->FindClass("com/android/terminal/TerminalCallbacks"));
    696 
    697     android::terminalCallbacksClass = reinterpret_cast<jclass>(env->NewGlobalRef(localClass.get()));
    698 
    699     android::damageMethod = env->GetMethodID(terminalCallbacksClass, "damage", "(IIII)I");
    700     android::moveRectMethod = env->GetMethodID(terminalCallbacksClass, "moveRect", "(IIIIIIII)I");
    701     android::moveCursorMethod = env->GetMethodID(terminalCallbacksClass, "moveCursor",
    702             "(IIIII)I");
    703     android::setTermPropBooleanMethod = env->GetMethodID(terminalCallbacksClass,
    704             "setTermPropBoolean", "(IZ)I");
    705     android::setTermPropIntMethod = env->GetMethodID(terminalCallbacksClass, "setTermPropInt",
    706             "(II)I");
    707     android::setTermPropStringMethod = env->GetMethodID(terminalCallbacksClass, "setTermPropString",
    708             "(ILjava/lang/String;)I");
    709     android::setTermPropColorMethod = env->GetMethodID(terminalCallbacksClass, "setTermPropColor",
    710             "(IIII)I");
    711     android::bellMethod = env->GetMethodID(terminalCallbacksClass, "bell", "()I");
    712 
    713     ScopedLocalRef<jclass> cellRunLocal(env,
    714             env->FindClass("com/android/terminal/Terminal$CellRun"));
    715     cellRunClass = reinterpret_cast<jclass>(env->NewGlobalRef(cellRunLocal.get()));
    716     cellRunDataField = env->GetFieldID(cellRunClass, "data", "[C");
    717     cellRunDataSizeField = env->GetFieldID(cellRunClass, "dataSize", "I");
    718     cellRunColSizeField = env->GetFieldID(cellRunClass, "colSize", "I");
    719     cellRunFgField = env->GetFieldID(cellRunClass, "fg", "I");
    720     cellRunBgField = env->GetFieldID(cellRunClass, "bg", "I");
    721 
    722     return jniRegisterNativeMethods(env, "com/android/terminal/Terminal",
    723             gMethods, NELEM(gMethods));
    724 }
    725 
    726 } /* namespace android */
    727