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