OLD | NEW |
(Empty) | |
| 1 /* Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 * Use of this source code is governed by a BSD-style license that can be |
| 3 * found in the LICENSE file. */ |
| 4 |
| 5 #include "ppapi_simple/ps_instance.h" |
| 6 |
| 7 #include <alloca.h> |
| 8 #include <assert.h> |
| 9 #include <ctype.h> |
| 10 #include <errno.h> |
| 11 #include <fcntl.h> |
| 12 #include <pthread.h> |
| 13 #include <stdio.h> |
| 14 #include <stdlib.h> |
| 15 #include <string.h> |
| 16 #include <sys/ioctl.h> |
| 17 #include <unistd.h> |
| 18 |
| 19 #include <ppapi/c/pp_errors.h> |
| 20 #include <ppapi/c/pp_instance.h> |
| 21 #include <ppapi/c/pp_module.h> |
| 22 #include <ppapi/c/pp_rect.h> |
| 23 #include <ppapi/c/pp_size.h> |
| 24 #include <ppapi/c/ppb.h> |
| 25 #include <ppapi/c/ppp.h> |
| 26 #include <ppapi/c/ppp_graphics_3d.h> |
| 27 #include <ppapi/c/ppp_input_event.h> |
| 28 #include <ppapi/c/ppp_instance.h> |
| 29 #include <ppapi/c/ppp_messaging.h> |
| 30 #include <ppapi/c/ppp_mouse_lock.h> |
| 31 |
| 32 #include "nacl_io/ioctl.h" |
| 33 #include "nacl_io/nacl_io.h" |
| 34 #include "nacl_io/log.h" |
| 35 #include "ppapi_simple/ps_interface.h" |
| 36 #include "ppapi_simple/ps_main.h" |
| 37 |
| 38 struct StartInfo { |
| 39 uint32_t argc_; |
| 40 char** argv_; |
| 41 }; |
| 42 |
| 43 PP_Instance g_ps_instance; |
| 44 PPB_GetInterface g_ps_get_interface; |
| 45 PSMainFunc_t g_ps_main_cb; |
| 46 |
| 47 static enum PSVerbosity s_verbosity; |
| 48 |
| 49 /* TTY handling */ |
| 50 static int s_tty_fd; |
| 51 static const char* s_tty_prefix; |
| 52 |
| 53 /* Condition variable and lock used to wait for exit confirmation from |
| 54 * JavaScript. */ |
| 55 static pthread_cond_t s_exit_cond; |
| 56 static pthread_mutex_t s_exit_lock; |
| 57 |
| 58 /* A message to Post to JavaScript instead of exiting, or NULL if exit() should |
| 59 * be called instead. */ |
| 60 static char* s_exit_message; |
| 61 |
| 62 static int ProcessProperties(void); |
| 63 ssize_t TtyOutputHandler(const char* buf, size_t count, void* user_data); |
| 64 static void MessageHandlerExit(struct PP_Var key, |
| 65 struct PP_Var value, |
| 66 void* user_data); |
| 67 static void MessageHandlerInput(struct PP_Var key, |
| 68 struct PP_Var value, |
| 69 void* user_data); |
| 70 static void MessageHandlerResize(struct PP_Var key, |
| 71 struct PP_Var value, |
| 72 void* user_data); |
| 73 static void HandleResize(int width, int height); |
| 74 static void* MainThread(void* info); |
| 75 static void ExitHandshake(int status, void* user_data); |
| 76 |
| 77 static void PostMessageString(const char* message) { |
| 78 struct PP_Var message_var = |
| 79 PSInterfaceVar()->VarFromUtf8(message, strlen(message)); |
| 80 PSInterfaceMessaging()->PostMessage(g_ps_instance, message_var); |
| 81 PSInterfaceVar()->Release(message_var); |
| 82 } |
| 83 |
| 84 static PP_Bool Instance_DidCreate(PP_Instance instance, |
| 85 uint32_t argc, |
| 86 const char* argn[], |
| 87 const char* argv[]) { |
| 88 g_ps_instance = instance; |
| 89 g_ps_main_cb = PSUserMainGet(); |
| 90 s_verbosity = PSV_LOG; |
| 91 PSInterfaceInputEvent()->RequestInputEvents( |
| 92 g_ps_instance, PP_INPUTEVENT_CLASS_MOUSE | PP_INPUTEVENT_CLASS_KEYBOARD | |
| 93 PP_INPUTEVENT_CLASS_WHEEL | PP_INPUTEVENT_CLASS_TOUCH); |
| 94 |
| 95 uint32_t i; |
| 96 struct StartInfo* si = malloc(sizeof(struct StartInfo)); |
| 97 |
| 98 si->argc_ = 0; |
| 99 si->argv_ = calloc(argc + 1, sizeof(char*)); |
| 100 si->argv_[0] = NULL; |
| 101 |
| 102 /* Process embed attributes into the environment. |
| 103 * Convert the attribute names to uppercase as environment variables are case |
| 104 * sensitive but are almost universally uppercase in practice. */ |
| 105 for (i = 0; i < argc; i++) { |
| 106 char* key = strdup(argn[i]); |
| 107 char* c = key; |
| 108 while (*c) { |
| 109 *c = toupper((int)*c); |
| 110 ++c; |
| 111 } |
| 112 setenv(key, argv[i], 1); |
| 113 free(key); |
| 114 } |
| 115 |
| 116 /* Set a default value for SRC. */ |
| 117 setenv("SRC", "NMF?", 0); |
| 118 /* Use the src tag name if ARG0 is not explicitly specified. */ |
| 119 setenv("ARG0", getenv("SRC"), 0); |
| 120 |
| 121 /* Walk ARG0..ARGn populating argv until an argument is missing. */ |
| 122 for (;;) { |
| 123 char arg_name[32]; |
| 124 snprintf(arg_name, 32, "ARG%d", si->argc_); |
| 125 const char* next_arg = getenv(arg_name); |
| 126 if (NULL == next_arg) |
| 127 break; |
| 128 |
| 129 si->argv_[si->argc_++] = strdup(next_arg); |
| 130 } |
| 131 |
| 132 int props_processed = ProcessProperties(); |
| 133 |
| 134 /* Log arg values only once ProcessProperties has been called so that the |
| 135 * PS_VERBOSITY attribute will be in effect. */ |
| 136 for (i = 0; i < argc; i++) { |
| 137 if (argv[i]) { |
| 138 PSInstanceTrace("attribs[%d] '%s=%s'\n", i, argn[i], argv[i]); |
| 139 } else { |
| 140 PSInstanceTrace("attribs[%d] '%s'\n", i, argn[i]); |
| 141 } |
| 142 } |
| 143 |
| 144 for (i = 0; i < si->argc_; i++) { |
| 145 PSInstanceTrace("argv[%d] '%s'\n", i, si->argv_[i]); |
| 146 } |
| 147 |
| 148 if (!props_processed) { |
| 149 PSInstanceWarn("Skipping create thread.\n"); |
| 150 return PP_FALSE; |
| 151 } |
| 152 |
| 153 pthread_t main_thread; |
| 154 int ret = pthread_create(&main_thread, NULL, MainThread, si); |
| 155 PSInstanceTrace("Created thread: %d.\n", ret); |
| 156 return ret == 0 ? PP_TRUE : PP_FALSE; |
| 157 } |
| 158 |
| 159 int ProcessProperties(void) { |
| 160 /* Reset verbosity if passed in */ |
| 161 const char* verbosity = getenv("PS_VERBOSITY"); |
| 162 if (verbosity) |
| 163 PSInstanceSetVerbosity(atoi(verbosity)); |
| 164 |
| 165 /* Enable NaCl IO to map STDIN, STDOUT, and STDERR */ |
| 166 nacl_io_init_ppapi(PSGetInstanceId(), PSGetInterface); |
| 167 |
| 168 s_tty_prefix = getenv("PS_TTY_PREFIX"); |
| 169 if (s_tty_prefix) { |
| 170 s_tty_fd = open("/dev/tty", O_WRONLY); |
| 171 if (s_tty_fd >= 0) { |
| 172 PSEventRegisterMessageHandler(s_tty_prefix, MessageHandlerInput, NULL); |
| 173 const char* tty_resize = getenv("PS_TTY_RESIZE"); |
| 174 if (tty_resize) |
| 175 PSEventRegisterMessageHandler(tty_resize, MessageHandlerResize, NULL); |
| 176 |
| 177 char* tty_rows = getenv("PS_TTY_ROWS"); |
| 178 char* tty_cols = getenv("PS_TTY_COLS"); |
| 179 if (tty_rows && tty_cols) { |
| 180 char* end = tty_rows; |
| 181 int rows = strtol(tty_rows, &end, 10); |
| 182 if (*end != '\0' || rows < 0) { |
| 183 PSInstanceError("Invalid value for PS_TTY_ROWS: %s\n", tty_rows); |
| 184 } else { |
| 185 end = tty_cols; |
| 186 int cols = strtol(tty_cols, &end, 10); |
| 187 if (*end != '\0' || cols < 0) |
| 188 PSInstanceError("Invalid value for PS_TTY_COLS: %s\n", tty_cols); |
| 189 else |
| 190 HandleResize(cols, rows); |
| 191 } |
| 192 } else if (tty_rows || tty_cols) { |
| 193 PSInstanceError("PS_TTY_ROWS and PS_TTY_COLS must be set together\n"); |
| 194 } |
| 195 |
| 196 struct tioc_nacl_output handler; |
| 197 handler.handler = TtyOutputHandler; |
| 198 handler.user_data = NULL; |
| 199 ioctl(s_tty_fd, TIOCNACLOUTPUT, &handler); |
| 200 } else { |
| 201 PSInstanceError("Failed to open /dev/tty.\n"); |
| 202 } |
| 203 } |
| 204 |
| 205 /* Set default values */ |
| 206 setenv("PS_STDIN", "/dev/stdin", 0); |
| 207 setenv("PS_STDOUT", "/dev/stdout", 0); |
| 208 setenv("PS_STDERR", "/dev/console3", 0); |
| 209 |
| 210 int fd0 = open(getenv("PS_STDIN"), O_RDONLY); |
| 211 dup2(fd0, 0); |
| 212 |
| 213 int fd1 = open(getenv("PS_STDOUT"), O_WRONLY); |
| 214 dup2(fd1, 1); |
| 215 |
| 216 int fd2 = open(getenv("PS_STDERR"), O_WRONLY); |
| 217 dup2(fd2, 2); |
| 218 |
| 219 PSEventRegisterMessageHandler("jspipe1", MessageHandlerInput, NULL); |
| 220 PSEventRegisterMessageHandler("jspipe2", MessageHandlerInput, NULL); |
| 221 PSEventRegisterMessageHandler("jspipe3", MessageHandlerInput, NULL); |
| 222 |
| 223 s_exit_message = getenv("PS_EXIT_MESSAGE"); |
| 224 |
| 225 /* If PS_EXIT_MESSAGE is set in the environment then we perform a handshake |
| 226 * with JavaScript when program exits. */ |
| 227 if (s_exit_message != NULL) |
| 228 nacl_io_set_exit_callback(ExitHandshake, NULL); |
| 229 |
| 230 /* Set line buffering on stdout and stderr */ |
| 231 #if !defined(WIN32) |
| 232 setvbuf(stderr, NULL, _IOLBF, 0); |
| 233 setvbuf(stdout, NULL, _IOLBF, 0); |
| 234 #endif |
| 235 return 1; |
| 236 } |
| 237 |
| 238 ssize_t TtyOutputHandler(const char* data, size_t count, void* user_data) { |
| 239 /* We prepend the s_tty_prefix to the data in buf, then package it up and |
| 240 * post it as a message to javascript. */ |
| 241 size_t tty_prefix_len = strlen(s_tty_prefix); |
| 242 char* message = alloca(tty_prefix_len + count + 1); |
| 243 memcpy(message, s_tty_prefix, tty_prefix_len); |
| 244 memcpy(message + tty_prefix_len, data, count); |
| 245 message[tty_prefix_len + count] = 0; |
| 246 PostMessageString(message); |
| 247 return count; |
| 248 } |
| 249 |
| 250 void MessageHandlerExit(struct PP_Var key, |
| 251 struct PP_Var value, |
| 252 void* user_data) { |
| 253 pthread_mutex_lock(&s_exit_lock); |
| 254 pthread_cond_signal(&s_exit_cond); |
| 255 pthread_mutex_unlock(&s_exit_lock); |
| 256 } |
| 257 |
| 258 void MessageHandlerInput(struct PP_Var key, |
| 259 struct PP_Var value, |
| 260 void* user_data) { |
| 261 uint32_t key_len; |
| 262 const char* key_str = PSInterfaceVar()->VarToUtf8(key, &key_len); |
| 263 |
| 264 const char* filename = NULL; |
| 265 if (strncmp(key_str, s_tty_prefix, key_len) == 0) { |
| 266 filename = "/dev/tty"; |
| 267 } else if (strncmp(key_str, "jspipe1", key_len) == 0) { |
| 268 filename = "/dev/jspipe1"; |
| 269 } else if (strncmp(key_str, "jspipe2", key_len) == 0) { |
| 270 filename = "/dev/jspipe2"; |
| 271 } else if (strncmp(key_str, "jspipe3", key_len) == 0) { |
| 272 filename = "/dev/jspipe3"; |
| 273 } else { |
| 274 PSInstanceError("unexpected input key: %s", key_str); |
| 275 return; |
| 276 } |
| 277 |
| 278 int fd = open(filename, O_RDONLY); |
| 279 if (fd < 0) { |
| 280 PSInstanceError("error opening file: %s (%s)", filename, strerror(errno)); |
| 281 return; |
| 282 } |
| 283 |
| 284 int ret = ioctl(fd, NACL_IOC_HANDLEMESSAGE, &value); |
| 285 if (ret != 0) { |
| 286 PSInstanceError("ioctl on %s failed: %d.\n", filename, ret); |
| 287 close(fd); |
| 288 return; |
| 289 } |
| 290 |
| 291 close(fd); |
| 292 } |
| 293 |
| 294 void MessageHandlerResize(struct PP_Var key, |
| 295 struct PP_Var value, |
| 296 void* user_data) { |
| 297 assert(value.type == PP_VARTYPE_ARRAY); |
| 298 assert(PSInterfaceVarArray()->GetLength(value) == 2); |
| 299 |
| 300 struct PP_Var width_var = PSInterfaceVarArray()->Get(value, 0); |
| 301 struct PP_Var height_var = PSInterfaceVarArray()->Get(value, 1); |
| 302 |
| 303 assert(width_var.type == PP_VARTYPE_INT32); |
| 304 assert(height_var.type == PP_VARTYPE_INT32); |
| 305 |
| 306 int width = width_var.value.as_int; |
| 307 int height = height_var.value.as_int; |
| 308 |
| 309 HandleResize(width, height); |
| 310 } |
| 311 |
| 312 void HandleResize(int width, int height) { |
| 313 struct winsize size; |
| 314 memset(&size, 0, sizeof(size)); |
| 315 size.ws_col = width; |
| 316 size.ws_row = height; |
| 317 ioctl(s_tty_fd, TIOCSWINSZ, &size); |
| 318 } |
| 319 |
| 320 void* MainThread(void* info) { |
| 321 int ret; |
| 322 uint32_t i; |
| 323 PSInstanceTrace("Running MainThread.\n"); |
| 324 struct StartInfo* si = (struct StartInfo*)info; |
| 325 |
| 326 PP_Resource message_loop = PSInterfaceMessageLoop()->Create(g_ps_instance); |
| 327 if (PSInterfaceMessageLoop()->AttachToCurrentThread(message_loop) != PP_OK) { |
| 328 PSInstanceError("Unable to attach message loop to thread.\n"); |
| 329 return NULL; |
| 330 } |
| 331 |
| 332 if (!g_ps_main_cb) { |
| 333 PSInstanceError("No main defined.\n"); |
| 334 return 0; |
| 335 } |
| 336 |
| 337 PSInstanceTrace("Starting MAIN.\n"); |
| 338 ret = g_ps_main_cb(si->argc_, si->argv_); |
| 339 PSInstanceLog("Main thread returned with %d.\n", ret); |
| 340 |
| 341 /* Clean up StartInfo. */ |
| 342 for (i = 0; i < si->argc_; i++) { |
| 343 free(si->argv_[i]); |
| 344 } |
| 345 free(si->argv_); |
| 346 free(si); |
| 347 |
| 348 /* Exit the entire process once the 'main' thread returns. The error code |
| 349 * will be available to javascript via the exitcode parameter of the crash |
| 350 * event. */ |
| 351 #ifdef __native_client__ |
| 352 exit(ret); |
| 353 #else |
| 354 ExitHandshake(ret, NULL); |
| 355 #endif |
| 356 return NULL; |
| 357 } |
| 358 |
| 359 void ExitHandshake(int status, void* user_data) { |
| 360 if (s_exit_message == NULL) |
| 361 return; |
| 362 |
| 363 PSEventRegisterMessageHandler(s_exit_message, MessageHandlerExit, NULL); |
| 364 |
| 365 /* exit message + ':' + num + \0 */ |
| 366 size_t message_len = strlen(s_exit_message) + 1 + 11 + 1; |
| 367 char* message = alloca(message_len); |
| 368 snprintf(message, message_len, "%s:%d", s_exit_message, status); |
| 369 |
| 370 pthread_mutex_lock(&s_exit_lock); |
| 371 PostMessageString(message); |
| 372 pthread_cond_wait(&s_exit_cond, &s_exit_lock); |
| 373 pthread_mutex_unlock(&s_exit_lock); |
| 374 } |
| 375 |
| 376 static void Instance_DidDestroy(PP_Instance instance) { |
| 377 } |
| 378 |
| 379 static void Instance_DidChangeView(PP_Instance instance, PP_Resource view) { |
| 380 struct PP_Rect rect; |
| 381 if (PSInterfaceView()->GetRect(view, &rect)) { |
| 382 PSInstanceLog("Got View change: %d,%d\n", rect.size.width, |
| 383 rect.size.height); |
| 384 PSEventPostResource(PSE_INSTANCE_DIDCHANGEVIEW, view); |
| 385 } |
| 386 } |
| 387 |
| 388 static void Instance_DidChangeFocus(PP_Instance instance, PP_Bool has_focus) { |
| 389 PSInstanceLog("Got Focus change: %s\n", has_focus ? "FOCUS ON" : "FOCUS OFF"); |
| 390 PSEventPostBool(PSE_INSTANCE_DIDCHANGEFOCUS, has_focus ? PP_TRUE : PP_FALSE); |
| 391 } |
| 392 |
| 393 static PP_Bool Instance_HandleDocumentLoad(PP_Instance instance, |
| 394 PP_Resource url_loader) { |
| 395 return PP_FALSE; |
| 396 } |
| 397 |
| 398 static void Messaging_HandleMessage(PP_Instance instance, |
| 399 struct PP_Var message) { |
| 400 PSInstanceTrace("Got Message\n"); |
| 401 PSEventPostVar(PSE_INSTANCE_HANDLEMESSAGE, message); |
| 402 } |
| 403 |
| 404 static PP_Bool InputEvent_HandleInputEvent(PP_Instance instance, |
| 405 PP_Resource input_event) { |
| 406 PSEventPostResource(PSE_INSTANCE_HANDLEINPUT, input_event); |
| 407 return PP_TRUE; |
| 408 } |
| 409 |
| 410 static void MouseLock_MouseLockLost(PP_Instance instance) { |
| 411 PSInstanceLog("MouseLockLost\n"); |
| 412 PSEventPost(PSE_MOUSELOCK_MOUSELOCKLOST); |
| 413 } |
| 414 |
| 415 static void Graphics3D_Graphics3DContextLost(PP_Instance instance) { |
| 416 PSInstanceLog("Graphics3DContextLost\n"); |
| 417 PSEventPost(PSE_GRAPHICS3D_GRAPHICS3DCONTEXTLOST); |
| 418 } |
| 419 |
| 420 void PSInstanceSetVerbosity(enum PSVerbosity verbosity) { |
| 421 s_verbosity = verbosity; |
| 422 } |
| 423 |
| 424 static void VALog(enum PSVerbosity verbosity, const char* fmt, va_list args) { |
| 425 if (verbosity <= s_verbosity) { |
| 426 fprintf(stderr, "ps: "); |
| 427 vfprintf(stderr, fmt, args); |
| 428 } |
| 429 } |
| 430 |
| 431 void PSInstanceTrace(const char* fmt, ...) { |
| 432 va_list ap; |
| 433 va_start(ap, fmt); |
| 434 VALog(PSV_TRACE, fmt, ap); |
| 435 va_end(ap); |
| 436 } |
| 437 |
| 438 void PSInstanceLog(const char* fmt, ...) { |
| 439 va_list ap; |
| 440 va_start(ap, fmt); |
| 441 VALog(PSV_LOG, fmt, ap); |
| 442 va_end(ap); |
| 443 } |
| 444 |
| 445 void PSInstanceWarn(const char* fmt, ...) { |
| 446 va_list ap; |
| 447 va_start(ap, fmt); |
| 448 VALog(PSV_WARN, fmt, ap); |
| 449 va_end(ap); |
| 450 } |
| 451 |
| 452 void PSInstanceError(const char* fmt, ...) { |
| 453 va_list ap; |
| 454 va_start(ap, fmt); |
| 455 VALog(PSV_ERROR, fmt, ap); |
| 456 va_end(ap); |
| 457 } |
| 458 |
| 459 const void* PSGetInterfaceImplementation(const char* interface_name) { |
| 460 if (strcmp(interface_name, PPP_INSTANCE_INTERFACE_1_1) == 0) { |
| 461 static struct PPP_Instance_1_1 interface = { |
| 462 &Instance_DidCreate, |
| 463 &Instance_DidDestroy, |
| 464 &Instance_DidChangeView, |
| 465 &Instance_DidChangeFocus, |
| 466 &Instance_HandleDocumentLoad, |
| 467 }; |
| 468 return &interface; |
| 469 } else if (strcmp(interface_name, PPP_MESSAGING_INTERFACE_1_0) == 0) { |
| 470 static struct PPP_Messaging_1_0 interface = { |
| 471 &Messaging_HandleMessage, |
| 472 }; |
| 473 return &interface; |
| 474 } else if (strcmp(interface_name, PPP_INPUT_EVENT_INTERFACE_0_1) == 0) { |
| 475 static struct PPP_InputEvent_0_1 interface = { |
| 476 &InputEvent_HandleInputEvent, |
| 477 }; |
| 478 return &interface; |
| 479 } else if (strcmp(interface_name, PPP_MOUSELOCK_INTERFACE_1_0) == 0) { |
| 480 static struct PPP_MouseLock_1_0 interface = { |
| 481 &MouseLock_MouseLockLost, |
| 482 }; |
| 483 return &interface; |
| 484 } else if (strcmp(interface_name, PPP_GRAPHICS_3D_INTERFACE_1_0) == 0) { |
| 485 static struct PPP_Graphics3D_1_0 interface = { |
| 486 &Graphics3D_Graphics3DContextLost, |
| 487 }; |
| 488 return &interface; |
| 489 } |
| 490 |
| 491 return NULL; |
| 492 } |
OLD | NEW |