com_android_terminal_Terminal.cpp revision de15e79aadde33fd8c880c19bd4fc6caca0bf795
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 "forkpty.h" 21#include "jni.h" 22#include "JNIHelp.h" 23#include "ScopedLocalRef.h" 24#include "ScopedPrimitiveArray.h" 25 26#include <fcntl.h> 27#include <stdio.h> 28#include <termios.h> 29#include <unistd.h> 30#include <util.h> 31#include <utmp.h> 32 33#include <vterm.h> 34 35#include <string.h> 36 37#define USE_TEST_SHELL 1 38#define DEBUG_CALLBACKS 0 39#define DEBUG_IO 0 40 41namespace android { 42 43/* 44 * JavaVM reference 45 */ 46static JavaVM* gJavaVM; 47 48/* 49 * Callback class reference 50 */ 51static jclass terminalCallbacksClass; 52 53/* 54 * Callback methods 55 */ 56static jmethodID damageMethod; 57static jmethodID prescrollMethod; 58static jmethodID moveRectMethod; 59static jmethodID moveCursorMethod; 60static jmethodID setTermPropBooleanMethod; 61static jmethodID setTermPropIntMethod; 62static jmethodID setTermPropStringMethod; 63static jmethodID setTermPropColorMethod; 64static jmethodID bellMethod; 65static jmethodID resizeMethod; 66 67/* 68 * CellRun class 69 */ 70static jclass cellRunClass; 71static jfieldID cellRunDataField; 72static jfieldID cellRunDataSizeField; 73static jfieldID cellRunColSizeField; 74static jfieldID cellRunFgField; 75static jfieldID cellRunBgField; 76 77/* 78 * Terminal session 79 */ 80class Terminal { 81public: 82 Terminal(jobject callbacks, int rows, int cols); 83 ~Terminal(); 84 85 int run(); 86 int stop(); 87 88 size_t write(const char *bytes, size_t len); 89 90 int flushDamage(); 91 int resize(short unsigned int rows, short unsigned int cols); 92 93 int getCell(VTermPos pos, VTermScreenCell* cell); 94 95 int getRows() const; 96 int getCols() const; 97 98 jobject getCallbacks() const; 99 100private: 101 int mMasterFd; 102 VTerm *mVt; 103 VTermScreen *mVts; 104 105 jobject mCallbacks; 106 short unsigned int mRows; 107 short unsigned int mCols; 108 bool mStopped; 109}; 110 111static JNIEnv* getEnv() { 112 JNIEnv* env; 113 114 if (gJavaVM->AttachCurrentThread(&env, NULL) < 0) { 115 return NULL; 116 } 117 118 return env; 119} 120 121/* 122 * VTerm event handlers 123 */ 124 125static int term_damage(VTermRect rect, void *user) { 126 Terminal* term = reinterpret_cast<Terminal*>(user); 127#if DEBUG_CALLBACKS 128 ALOGW("term_damage"); 129#endif 130 131 JNIEnv* env = getEnv(); 132 if (env == NULL) { 133 ALOGE("term_damage: couldn't get JNIEnv"); 134 return 0; 135 } 136 137 return env->CallIntMethod(term->getCallbacks(), damageMethod, rect.start_row, rect.end_row, 138 rect.start_col, rect.end_col); 139} 140 141static int term_prescroll(VTermRect rect, void *user) { 142 Terminal* term = reinterpret_cast<Terminal*>(user); 143#if DEBUG_CALLBACKS 144 ALOGW("term_prescroll"); 145#endif 146 147 JNIEnv* env = getEnv(); 148 if (env == NULL) { 149 ALOGE("term_prescroll: couldn't get JNIEnv"); 150 return 0; 151 } 152 153 return env->CallIntMethod(term->getCallbacks(), prescrollMethod, rect.start_row, rect.end_row, 154 rect.start_col, rect.end_col); 155} 156 157static int term_moverect(VTermRect dest, VTermRect src, void *user) { 158 Terminal* term = reinterpret_cast<Terminal*>(user); 159#if DEBUG_CALLBACKS 160 ALOGW("term_moverect"); 161#endif 162 163 JNIEnv* env = getEnv(); 164 if (env == NULL) { 165 ALOGE("term_moverect: couldn't get JNIEnv"); 166 return 0; 167 } 168 169 return env->CallIntMethod(term->getCallbacks(), moveRectMethod, 170 dest.start_row, dest.end_row, dest.start_col, dest.end_col, 171 src.start_row, src.end_row, src.start_col, src.end_col); 172} 173 174static int term_movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user) { 175 Terminal* term = reinterpret_cast<Terminal*>(user); 176#if DEBUG_CALLBACKS 177 ALOGW("term_movecursor"); 178#endif 179 180 JNIEnv* env = getEnv(); 181 if (env == NULL) { 182 ALOGE("term_movecursor: couldn't get JNIEnv"); 183 return 0; 184 } 185 186 return env->CallIntMethod(term->getCallbacks(), moveCursorMethod, pos.row, 187 pos.col, oldpos.row, oldpos.col, visible); 188} 189 190static int term_settermprop(VTermProp prop, VTermValue *val, void *user) { 191 Terminal* term = reinterpret_cast<Terminal*>(user); 192#if DEBUG_CALLBACKS 193 ALOGW("term_settermprop"); 194#endif 195 196 JNIEnv* env = getEnv(); 197 if (env == NULL) { 198 ALOGE("term_settermprop: couldn't get JNIEnv"); 199 return 0; 200 } 201 202 switch (vterm_get_prop_type(prop)) { 203 case VTERM_VALUETYPE_BOOL: 204 return env->CallIntMethod(term->getCallbacks(), setTermPropBooleanMethod, 205 static_cast<jboolean>(val->boolean)); 206 case VTERM_VALUETYPE_INT: 207 return env->CallIntMethod(term->getCallbacks(), setTermPropIntMethod, prop, val->number); 208 case VTERM_VALUETYPE_STRING: 209 return env->CallIntMethod(term->getCallbacks(), setTermPropStringMethod, prop, 210 env->NewStringUTF(val->string)); 211 case VTERM_VALUETYPE_COLOR: 212 return env->CallIntMethod(term->getCallbacks(), setTermPropIntMethod, prop, val->color.red, 213 val->color.green, val->color.blue); 214 default: 215 ALOGE("unknown callback type"); 216 return 0; 217 } 218} 219 220static int term_setmousefunc(VTermMouseFunc func, void *data, void *user) { 221 Terminal* term = reinterpret_cast<Terminal*>(user); 222#if DEBUG_CALLBACKS 223 ALOGW("term_setmousefunc"); 224#endif 225 return 1; 226} 227 228static int term_bell(void *user) { 229 Terminal* term = reinterpret_cast<Terminal*>(user); 230#if DEBUG_CALLBACKS 231 ALOGW("term_bell"); 232#endif 233 234 JNIEnv* env = getEnv(); 235 if (env == NULL) { 236 ALOGE("term_bell: couldn't get JNIEnv"); 237 return 0; 238 } 239 240 return env->CallIntMethod(term->getCallbacks(), bellMethod); 241} 242 243static int term_resize(int rows, int cols, void *user) { 244 Terminal* term = reinterpret_cast<Terminal*>(user); 245#if DEBUG_CALLBACKS 246 ALOGW("term_resize"); 247#endif 248 249 JNIEnv* env = getEnv(); 250 if (env == NULL) { 251 ALOGE("term_bell: couldn't get JNIEnv"); 252 return 0; 253 } 254 255 return env->CallIntMethod(term->getCallbacks(), resizeMethod, rows, cols); 256} 257 258static VTermScreenCallbacks cb = { 259 .damage = term_damage, 260 .prescroll = term_prescroll, 261 .moverect = term_moverect, 262 .movecursor = term_movecursor, 263 .settermprop = term_settermprop, 264 .setmousefunc = term_setmousefunc, 265 .bell = term_bell, 266 .resize = term_resize, 267}; 268 269Terminal::Terminal(jobject callbacks, int rows, int cols) : 270 mCallbacks(callbacks), mRows(rows), mCols(cols), mStopped(false) { 271 /* Create VTerm */ 272 mVt = vterm_new(rows, cols); 273 vterm_parser_set_utf8(mVt, 1); 274 275 /* Set up screen */ 276 mVts = vterm_obtain_screen(mVt); 277 vterm_screen_enable_altscreen(mVts, 1); 278 vterm_screen_set_callbacks(mVts, &cb, this); 279 vterm_screen_set_damage_merge(mVts, VTERM_DAMAGE_SCROLL); 280 vterm_screen_reset(mVts, 1); 281} 282 283Terminal::~Terminal() { 284 close(mMasterFd); 285 vterm_free(mVt); 286} 287 288int Terminal::run() { 289 /* None of the docs about termios explain how to construct a new one of 290 * these, so this is largely a guess */ 291 struct termios termios = { 292 .c_iflag = ICRNL|IXON|IUTF8, 293 .c_oflag = OPOST|ONLCR|NL0|CR0|TAB0|BS0|VT0|FF0, 294 .c_cflag = CS8|CREAD, 295 .c_lflag = ISIG|ICANON|IEXTEN|ECHO|ECHOE|ECHOK, 296 /* c_cc later */ 297 }; 298 299 cfsetispeed(&termios, B38400); 300 cfsetospeed(&termios, B38400); 301 302 termios.c_cc[VINTR] = 0x1f & 'C'; 303 termios.c_cc[VQUIT] = 0x1f & '\\'; 304 termios.c_cc[VERASE] = 0x7f; 305 termios.c_cc[VKILL] = 0x1f & 'U'; 306 termios.c_cc[VEOF] = 0x1f & 'D'; 307 termios.c_cc[VSTART] = 0x1f & 'Q'; 308 termios.c_cc[VSTOP] = 0x1f & 'S'; 309 termios.c_cc[VSUSP] = 0x1f & 'Z'; 310 termios.c_cc[VREPRINT] = 0x1f & 'R'; 311 termios.c_cc[VWERASE] = 0x1f & 'W'; 312 termios.c_cc[VLNEXT] = 0x1f & 'V'; 313 termios.c_cc[VMIN] = 1; 314 termios.c_cc[VTIME] = 0; 315 316 struct winsize size = { mRows, mCols, 0, 0 }; 317 318 int stderr_save_fd = dup(2); 319 if (stderr_save_fd < 0) { 320 ALOGE("failed to dup stderr - %s", strerror(errno)); 321 } 322 323 pid_t kid = forkpty(&mMasterFd, NULL, &termios, &size); 324 if (kid == 0) { 325 /* Restore the ISIG signals back to defaults */ 326 signal(SIGINT, SIG_DFL); 327 signal(SIGQUIT, SIG_DFL); 328 signal(SIGSTOP, SIG_DFL); 329 signal(SIGCONT, SIG_DFL); 330 331 FILE *stderr_save = fdopen(stderr_save_fd, "a"); 332 333 if (!stderr_save) { 334 ALOGE("failed to open stderr - %s", strerror(errno)); 335 } 336 337 char *shell = "/system/bin/sh"; //getenv("SHELL"); 338#if USE_TEST_SHELL 339 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}; 340#else 341 char *args[2] = {shell, NULL}; 342#endif 343 344 execvp(shell, args); 345 fprintf(stderr_save, "Cannot exec(%s) - %s\n", shell, strerror(errno)); 346 _exit(1); 347 } 348 349 ALOGD("entering read() loop"); 350 while (1) { 351 char buffer[4096]; 352 ssize_t bytes = ::read(mMasterFd, buffer, sizeof buffer); 353#if DEBUG_IO 354 ALOGD("read() returned %d bytes", bytes); 355#endif 356 357 if (mStopped) { 358 ALOGD("stop() requested"); 359 break; 360 } 361 if (bytes == 0) { 362 ALOGD("read() found EOF"); 363 break; 364 } 365 if (bytes == -1) { 366 ALOGE("read() failed: %s", strerror(errno)); 367 return 1; 368 } 369 370 vterm_push_bytes(mVt, buffer, bytes); 371 372 vterm_screen_flush_damage(mVts); 373 } 374 375 return 0; 376} 377 378int Terminal::stop() { 379 // TODO: explicitly kill forked child process 380 mStopped = true; 381 return 0; 382} 383 384size_t Terminal::write(const char *bytes, size_t len) { 385 return ::write(mMasterFd, bytes, len); 386} 387 388int Terminal::flushDamage() { 389 vterm_screen_flush_damage(mVts); 390 return 0; 391} 392 393int Terminal::resize(short unsigned int rows, short unsigned int cols) { 394 ALOGD("resize(%d, %d)", rows, cols); 395 396 mRows = rows; 397 mCols = cols; 398 399 struct winsize size = { rows, cols, 0, 0 }; 400 ioctl(mMasterFd, TIOCSWINSZ, &size); 401 402 vterm_set_size(mVt, rows, cols); 403 vterm_screen_flush_damage(mVts); 404 405 return 0; 406} 407 408int Terminal::getCell(VTermPos pos, VTermScreenCell* cell) { 409 return vterm_screen_get_cell(mVts, pos, cell); 410} 411 412int Terminal::getRows() const { 413 return mRows; 414} 415 416int Terminal::getCols() const { 417 return mCols; 418} 419 420jobject Terminal::getCallbacks() const { 421 return mCallbacks; 422} 423 424/* 425 * JNI glue 426 */ 427 428static jint com_android_terminal_Terminal_nativeInit(JNIEnv* env, jclass clazz, jobject callbacks, 429 jint rows, jint cols) { 430 return reinterpret_cast<jint>(new Terminal(env->NewGlobalRef(callbacks), rows, cols)); 431} 432 433static jint com_android_terminal_Terminal_nativeRun(JNIEnv* env, jclass clazz, jint ptr) { 434 Terminal* term = reinterpret_cast<Terminal*>(ptr); 435 return term->run(); 436} 437 438static jint com_android_terminal_Terminal_nativeStop(JNIEnv* env, jclass clazz, jint ptr) { 439 Terminal* term = reinterpret_cast<Terminal*>(ptr); 440 return term->stop(); 441} 442 443static jint com_android_terminal_Terminal_nativeFlushDamage(JNIEnv* env, jclass clazz, jint ptr) { 444 Terminal* term = reinterpret_cast<Terminal*>(ptr); 445 return term->flushDamage(); 446} 447 448static jint com_android_terminal_Terminal_nativeResize(JNIEnv* env, 449 jclass clazz, jint ptr, jint rows, jint cols) { 450 Terminal* term = reinterpret_cast<Terminal*>(ptr); 451 return term->resize(rows, cols); 452} 453 454static int toArgb(VTermColor* color) { 455 return 0xff << 24 | color->red << 16 | color->green << 8 | color->blue; 456} 457 458static bool isCellStyleEqual(VTermScreenCell* a, VTermScreenCell* b) { 459 // TODO: check other attrs beyond just color 460 if (toArgb(&a->fg) != toArgb(&b->fg)) { 461 return false; 462 } 463 if (toArgb(&a->bg) != toArgb(&b->bg)) { 464 return false; 465 } 466 return true; 467} 468 469static jint com_android_terminal_Terminal_nativeGetCellRun(JNIEnv* env, 470 jclass clazz, jint ptr, jint row, jint col, jobject run) { 471 Terminal* term = reinterpret_cast<Terminal*>(ptr); 472 473 jcharArray dataArray = (jcharArray) env->GetObjectField(run, cellRunDataField); 474 ScopedCharArrayRW data(env, dataArray); 475 if (data.get() == NULL) { 476 return -1; 477 } 478 479 VTermScreenCell prevCell, cell; 480 memset(&prevCell, 0, sizeof(VTermScreenCell)); 481 memset(&cell, 0, sizeof(VTermScreenCell)); 482 483 VTermPos pos = { 484 .row = row, 485 .col = col, 486 }; 487 488 unsigned int dataSize = 0; 489 unsigned int colSize = 0; 490 while (pos.col < term->getCols()) { 491 int res = term->getCell(pos, &cell); 492 493 if (colSize == 0) { 494 env->SetIntField(run, cellRunFgField, toArgb(&cell.fg)); 495 env->SetIntField(run, cellRunBgField, toArgb(&cell.bg)); 496 } else { 497 if (!isCellStyleEqual(&cell, &prevCell)) { 498 break; 499 } 500 } 501 memcpy(&prevCell, &cell, sizeof(VTermScreenCell)); 502 503 // TODO: remove this once terminal is resized 504 if (cell.width == 0) { 505 cell.width = 1; 506 } 507 508 // TODO: support full UTF-32 characters 509 // for testing, 0x00020000 should become 0xD840 0xDC00 510 unsigned int size = 1; 511 512 // Only include cell chars if they fit into run 513 if (dataSize + size <= data.size()) { 514 data[dataSize] = cell.chars[0]; 515 dataSize += size; 516 colSize += cell.width; 517 pos.col += cell.width; 518 } else { 519 break; 520 } 521 } 522 523 env->SetIntField(run, cellRunDataSizeField, dataSize); 524 env->SetIntField(run, cellRunColSizeField, colSize); 525 526 return 0; 527} 528 529static jint com_android_terminal_Terminal_nativeGetRows(JNIEnv* env, jclass clazz, jint ptr) { 530 Terminal* term = reinterpret_cast<Terminal*>(ptr); 531 return term->getRows(); 532} 533 534static jint com_android_terminal_Terminal_nativeGetCols(JNIEnv* env, jclass clazz, jint ptr) { 535 Terminal* term = reinterpret_cast<Terminal*>(ptr); 536 return term->getCols(); 537} 538 539static JNINativeMethod gMethods[] = { 540 { "nativeInit", "(Lcom/android/terminal/TerminalCallbacks;II)I", (void*)com_android_terminal_Terminal_nativeInit }, 541 { "nativeRun", "(I)I", (void*)com_android_terminal_Terminal_nativeRun }, 542 { "nativeStop", "(I)I", (void*)com_android_terminal_Terminal_nativeStop }, 543 { "nativeFlushDamage", "(I)I", (void*)com_android_terminal_Terminal_nativeFlushDamage }, 544 { "nativeResize", "(III)I", (void*)com_android_terminal_Terminal_nativeResize }, 545 { "nativeGetCellRun", "(IIILcom/android/terminal/Terminal$CellRun;)I", (void*)com_android_terminal_Terminal_nativeGetCellRun }, 546 { "nativeGetRows", "(I)I", (void*)com_android_terminal_Terminal_nativeGetRows }, 547 { "nativeGetCols", "(I)I", (void*)com_android_terminal_Terminal_nativeGetCols }, 548}; 549 550int register_com_android_terminal_Terminal(JNIEnv* env) { 551 ScopedLocalRef<jclass> localClass(env, 552 env->FindClass("com/android/terminal/TerminalCallbacks")); 553 554 android::terminalCallbacksClass = reinterpret_cast<jclass>(env->NewGlobalRef(localClass.get())); 555 556 android::damageMethod = env->GetMethodID(terminalCallbacksClass, "damage", "(IIII)I"); 557 android::prescrollMethod = env->GetMethodID(terminalCallbacksClass, "prescroll", "(IIII)I"); 558 android::moveRectMethod = env->GetMethodID(terminalCallbacksClass, "moveRect", "(IIIIIIII)I"); 559 android::moveCursorMethod = env->GetMethodID(terminalCallbacksClass, "moveCursor", 560 "(IIIII)I"); 561 android::setTermPropBooleanMethod = env->GetMethodID(terminalCallbacksClass, 562 "setTermPropBoolean", "(IZ)I"); 563 android::setTermPropIntMethod = env->GetMethodID(terminalCallbacksClass, "setTermPropInt", 564 "(II)I"); 565 android::setTermPropStringMethod = env->GetMethodID(terminalCallbacksClass, "setTermPropString", 566 "(ILjava/lang/String;)I"); 567 android::setTermPropColorMethod = env->GetMethodID(terminalCallbacksClass, "setTermPropColor", 568 "(IIII)I"); 569 android::bellMethod = env->GetMethodID(terminalCallbacksClass, "bell", "()I"); 570 android::resizeMethod = env->GetMethodID(terminalCallbacksClass, "resize", "(II)I"); 571 572 ScopedLocalRef<jclass> cellRunLocal(env, 573 env->FindClass("com/android/terminal/Terminal$CellRun")); 574 cellRunClass = reinterpret_cast<jclass>(env->NewGlobalRef(cellRunLocal.get())); 575 cellRunDataField = env->GetFieldID(cellRunClass, "data", "[C"); 576 cellRunDataSizeField = env->GetFieldID(cellRunClass, "dataSize", "I"); 577 cellRunColSizeField = env->GetFieldID(cellRunClass, "colSize", "I"); 578 cellRunFgField = env->GetFieldID(cellRunClass, "fg", "I"); 579 cellRunBgField = env->GetFieldID(cellRunClass, "bg", "I"); 580 581 env->GetJavaVM(&gJavaVM); 582 583 return jniRegisterNativeMethods(env, "com/android/terminal/Terminal", 584 gMethods, NELEM(gMethods)); 585} 586 587} /* namespace android */ 588