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. */
5 #include "ppapi_simple/ps_instance.h"
16 #include <sys/ioctl.h>
17 #include <termios.h> /* Needed for struct winsize in bionic */
20 #include <ppapi/c/pp_errors.h>
21 #include <ppapi/c/pp_instance.h>
22 #include <ppapi/c/pp_module.h>
23 #include <ppapi/c/pp_rect.h>
24 #include <ppapi/c/pp_size.h>
25 #include <ppapi/c/ppb.h>
26 #include <ppapi/c/ppp.h>
27 #include <ppapi/c/ppp_graphics_3d.h>
28 #include <ppapi/c/ppp_input_event.h>
29 #include <ppapi/c/ppp_instance.h>
30 #include <ppapi/c/ppp_messaging.h>
31 #include <ppapi/c/ppp_mouse_lock.h>
33 #include "nacl_io/ioctl.h"
34 #include "nacl_io/nacl_io.h"
35 #include "nacl_io/log.h"
36 #include "ppapi_simple/ps_interface.h"
37 #include "ppapi_simple/ps_internal.h"
38 #include "ppapi_simple/ps_main.h"
45 PP_Instance g_ps_instance
;
46 PPB_GetInterface g_ps_get_interface
;
47 PSMainFunc_t g_ps_main_cb
;
49 static enum PSVerbosity s_verbosity
;
53 static const char* s_tty_prefix
;
55 /* Condition variable and lock used to wait for exit confirmation from
57 static pthread_cond_t s_exit_cond
;
58 static pthread_mutex_t s_exit_lock
;
60 /* A message to Post to JavaScript instead of exiting, or NULL if exit() should
61 * be called instead. */
62 static char* s_exit_message
;
64 static int ProcessProperties(void);
65 ssize_t
TtyOutputHandler(const char* buf
, size_t count
, void* user_data
);
66 static void MessageHandlerExit(struct PP_Var key
,
69 static void MessageHandlerInput(struct PP_Var key
,
72 static void MessageHandlerResize(struct PP_Var key
,
75 static void HandleResize(int width
, int height
);
76 static void* MainThread(void* info
);
77 static void ExitHandshake(int status
, void* user_data
);
79 static void PostMessageString(const char* message
) {
80 struct PP_Var message_var
=
81 PSInterfaceVar()->VarFromUtf8(message
, strlen(message
));
82 PSInterfaceMessaging()->PostMessage(g_ps_instance
, message_var
);
83 PSInterfaceVar()->Release(message_var
);
86 static PP_Bool
Instance_DidCreate(PP_Instance instance
,
90 g_ps_instance
= instance
;
91 g_ps_main_cb
= PSUserMainGet();
92 s_verbosity
= PSV_LOG
;
93 PSInterfaceInputEvent()->RequestInputEvents(
94 g_ps_instance
, PP_INPUTEVENT_CLASS_MOUSE
| PP_INPUTEVENT_CLASS_KEYBOARD
|
95 PP_INPUTEVENT_CLASS_WHEEL
| PP_INPUTEVENT_CLASS_TOUCH
);
98 struct StartInfo
* si
= malloc(sizeof(struct StartInfo
));
101 si
->argv_
= calloc(argc
+ 1, sizeof(char*));
104 /* Process embed attributes into the environment.
105 * Convert the attribute names to uppercase as environment variables are case
106 * sensitive but are almost universally uppercase in practice. */
107 for (i
= 0; i
< argc
; i
++) {
108 char* key
= strdup(argn
[i
]);
111 *c
= toupper((int)*c
);
114 setenv(key
, argv
[i
], 1);
118 /* Set a default value for SRC. */
119 setenv("SRC", "NMF?", 0);
120 /* Use the src tag name if ARG0 is not explicitly specified. */
121 setenv("ARG0", getenv("SRC"), 0);
123 /* Walk ARG0..ARGn populating argv until an argument is missing. */
126 snprintf(arg_name
, 32, "ARG%d", si
->argc_
);
127 const char* next_arg
= getenv(arg_name
);
128 if (NULL
== next_arg
)
131 si
->argv_
[si
->argc_
++] = strdup(next_arg
);
134 int props_processed
= ProcessProperties();
136 /* Log arg values only once ProcessProperties has been called so that the
137 * PS_VERBOSITY attribute will be in effect. */
138 for (i
= 0; i
< argc
; i
++) {
140 PSInstanceTrace("attribs[%d] '%s=%s'\n", i
, argn
[i
], argv
[i
]);
142 PSInstanceTrace("attribs[%d] '%s'\n", i
, argn
[i
]);
146 for (i
= 0; i
< si
->argc_
; i
++) {
147 PSInstanceTrace("argv[%d] '%s'\n", i
, si
->argv_
[i
]);
150 if (!props_processed
) {
151 PSInstanceWarn("Skipping create thread.\n");
155 pthread_t main_thread
;
156 int ret
= pthread_create(&main_thread
, NULL
, MainThread
, si
);
157 PSInstanceTrace("Created thread: %d.\n", ret
);
158 return ret
== 0 ? PP_TRUE
: PP_FALSE
;
161 int ProcessProperties(void) {
162 /* Reset verbosity if passed in */
163 const char* verbosity
= getenv("PS_VERBOSITY");
165 PSInstanceSetVerbosity(atoi(verbosity
));
167 /* Enable NaCl IO to map STDIN, STDOUT, and STDERR */
168 nacl_io_init_ppapi(PSGetInstanceId(), PSGetInterface
);
170 s_tty_prefix
= getenv("PS_TTY_PREFIX");
172 s_tty_fd
= open("/dev/tty", O_WRONLY
);
174 PSEventRegisterMessageHandler(s_tty_prefix
, MessageHandlerInput
, NULL
);
175 const char* tty_resize
= getenv("PS_TTY_RESIZE");
177 PSEventRegisterMessageHandler(tty_resize
, MessageHandlerResize
, NULL
);
179 char* tty_rows
= getenv("PS_TTY_ROWS");
180 char* tty_cols
= getenv("PS_TTY_COLS");
181 if (tty_rows
&& tty_cols
) {
182 char* end
= tty_rows
;
183 int rows
= strtol(tty_rows
, &end
, 10);
184 if (*end
!= '\0' || rows
< 0) {
185 PSInstanceError("Invalid value for PS_TTY_ROWS: %s\n", tty_rows
);
188 int cols
= strtol(tty_cols
, &end
, 10);
189 if (*end
!= '\0' || cols
< 0)
190 PSInstanceError("Invalid value for PS_TTY_COLS: %s\n", tty_cols
);
192 HandleResize(cols
, rows
);
194 } else if (tty_rows
|| tty_cols
) {
195 PSInstanceError("PS_TTY_ROWS and PS_TTY_COLS must be set together\n");
198 struct tioc_nacl_output handler
;
199 handler
.handler
= TtyOutputHandler
;
200 handler
.user_data
= NULL
;
201 ioctl(s_tty_fd
, TIOCNACLOUTPUT
, &handler
);
203 PSInstanceError("Failed to open /dev/tty.\n");
207 /* Set default values */
208 setenv("PS_STDIN", "/dev/stdin", 0);
209 setenv("PS_STDOUT", "/dev/stdout", 0);
210 setenv("PS_STDERR", "/dev/console3", 0);
212 int fd0
= open(getenv("PS_STDIN"), O_RDONLY
);
215 int fd1
= open(getenv("PS_STDOUT"), O_WRONLY
);
218 int fd2
= open(getenv("PS_STDERR"), O_WRONLY
);
221 PSEventRegisterMessageHandler("jspipe1", MessageHandlerInput
, NULL
);
222 PSEventRegisterMessageHandler("jspipe2", MessageHandlerInput
, NULL
);
223 PSEventRegisterMessageHandler("jspipe3", MessageHandlerInput
, NULL
);
225 s_exit_message
= getenv("PS_EXIT_MESSAGE");
227 /* If PS_EXIT_MESSAGE is set in the environment then we perform a handshake
228 * with JavaScript when program exits. */
229 if (s_exit_message
!= NULL
)
230 nacl_io_set_exit_callback(ExitHandshake
, NULL
);
232 /* Set line buffering on stdout and stderr */
234 setvbuf(stderr
, NULL
, _IOLBF
, 0);
235 setvbuf(stdout
, NULL
, _IOLBF
, 0);
240 ssize_t
TtyOutputHandler(const char* data
, size_t count
, void* user_data
) {
241 /* We prepend the s_tty_prefix to the data in buf, then package it up and
242 * post it as a message to javascript. */
243 size_t tty_prefix_len
= strlen(s_tty_prefix
);
244 char* message
= alloca(tty_prefix_len
+ count
+ 1);
245 memcpy(message
, s_tty_prefix
, tty_prefix_len
);
246 memcpy(message
+ tty_prefix_len
, data
, count
);
247 message
[tty_prefix_len
+ count
] = 0;
248 PostMessageString(message
);
252 void MessageHandlerExit(struct PP_Var key
,
255 pthread_mutex_lock(&s_exit_lock
);
256 pthread_cond_signal(&s_exit_cond
);
257 pthread_mutex_unlock(&s_exit_lock
);
260 void MessageHandlerInput(struct PP_Var key
,
264 const char* key_str
= PSInterfaceVar()->VarToUtf8(key
, &key_len
);
266 const char* filename
= NULL
;
267 if (strncmp(key_str
, s_tty_prefix
, key_len
) == 0) {
268 filename
= "/dev/tty";
269 } else if (strncmp(key_str
, "jspipe1", key_len
) == 0) {
270 filename
= "/dev/jspipe1";
271 } else if (strncmp(key_str
, "jspipe2", key_len
) == 0) {
272 filename
= "/dev/jspipe2";
273 } else if (strncmp(key_str
, "jspipe3", key_len
) == 0) {
274 filename
= "/dev/jspipe3";
276 PSInstanceError("unexpected input key: %s", key_str
);
280 int fd
= open(filename
, O_RDONLY
);
282 PSInstanceError("error opening file: %s (%s)", filename
, strerror(errno
));
286 int ret
= ioctl(fd
, NACL_IOC_HANDLEMESSAGE
, &value
);
288 PSInstanceError("ioctl on %s failed: %d.\n", filename
, ret
);
296 void MessageHandlerResize(struct PP_Var key
,
299 assert(value
.type
== PP_VARTYPE_ARRAY
);
300 assert(PSInterfaceVarArray()->GetLength(value
) == 2);
302 struct PP_Var width_var
= PSInterfaceVarArray()->Get(value
, 0);
303 struct PP_Var height_var
= PSInterfaceVarArray()->Get(value
, 1);
305 assert(width_var
.type
== PP_VARTYPE_INT32
);
306 assert(height_var
.type
== PP_VARTYPE_INT32
);
308 int width
= width_var
.value
.as_int
;
309 int height
= height_var
.value
.as_int
;
311 HandleResize(width
, height
);
314 void HandleResize(int width
, int height
) {
316 memset(&size
, 0, sizeof(size
));
318 size
.ws_row
= height
;
319 ioctl(s_tty_fd
, TIOCSWINSZ
, &size
);
322 void* MainThread(void* info
) {
325 PSInstanceTrace("Running MainThread.\n");
326 struct StartInfo
* si
= (struct StartInfo
*)info
;
328 PP_Resource message_loop
= PSInterfaceMessageLoop()->Create(g_ps_instance
);
329 if (PSInterfaceMessageLoop()->AttachToCurrentThread(message_loop
) != PP_OK
) {
330 PSInstanceError("Unable to attach message loop to thread.\n");
335 PSInstanceError("No main defined.\n");
339 PSInstanceTrace("Starting MAIN.\n");
340 ret
= g_ps_main_cb(si
->argc_
, si
->argv_
);
341 PSInstanceLog("Main thread returned with %d.\n", ret
);
343 /* Clean up StartInfo. */
344 for (i
= 0; i
< si
->argc_
; i
++) {
350 /* Exit the entire process once the 'main' thread returns. The error code
351 * will be available to javascript via the exitcode parameter of the crash
353 #ifdef __native_client__
356 ExitHandshake(ret
, NULL
);
361 void ExitHandshake(int status
, void* user_data
) {
362 if (s_exit_message
== NULL
)
365 PSEventRegisterMessageHandler(s_exit_message
, MessageHandlerExit
, NULL
);
367 /* exit message + ':' + num + \0 */
368 size_t message_len
= strlen(s_exit_message
) + 1 + 11 + 1;
369 char* message
= alloca(message_len
);
370 snprintf(message
, message_len
, "%s:%d", s_exit_message
, status
);
372 pthread_mutex_lock(&s_exit_lock
);
373 PostMessageString(message
);
374 pthread_cond_wait(&s_exit_cond
, &s_exit_lock
);
375 pthread_mutex_unlock(&s_exit_lock
);
378 static void Instance_DidDestroy(PP_Instance instance
) {
381 static void Instance_DidChangeView(PP_Instance instance
, PP_Resource view
) {
383 if (PSInterfaceView()->GetRect(view
, &rect
)) {
384 PSInstanceLog("Got View change: %d,%d\n", rect
.size
.width
,
386 PSEventPostResource(PSE_INSTANCE_DIDCHANGEVIEW
, view
);
390 static void Instance_DidChangeFocus(PP_Instance instance
, PP_Bool has_focus
) {
391 PSInstanceLog("Got Focus change: %s\n", has_focus
? "FOCUS ON" : "FOCUS OFF");
392 PSEventPostBool(PSE_INSTANCE_DIDCHANGEFOCUS
, has_focus
? PP_TRUE
: PP_FALSE
);
395 static PP_Bool
Instance_HandleDocumentLoad(PP_Instance instance
,
396 PP_Resource url_loader
) {
400 static void Messaging_HandleMessage(PP_Instance instance
,
401 struct PP_Var message
) {
402 PSInstanceTrace("Got Message\n");
403 PSEventPostVar(PSE_INSTANCE_HANDLEMESSAGE
, message
);
406 static PP_Bool
InputEvent_HandleInputEvent(PP_Instance instance
,
407 PP_Resource input_event
) {
408 PSEventPostResource(PSE_INSTANCE_HANDLEINPUT
, input_event
);
412 static void MouseLock_MouseLockLost(PP_Instance instance
) {
413 PSInstanceLog("MouseLockLost\n");
414 PSEventPost(PSE_MOUSELOCK_MOUSELOCKLOST
);
417 static void Graphics3D_Graphics3DContextLost(PP_Instance instance
) {
418 PSInstanceLog("Graphics3DContextLost\n");
419 PSEventPost(PSE_GRAPHICS3D_GRAPHICS3DCONTEXTLOST
);
422 void PSInstanceSetVerbosity(enum PSVerbosity verbosity
) {
423 s_verbosity
= verbosity
;
426 static void VALog(enum PSVerbosity verbosity
, const char* fmt
, va_list args
) {
427 if (verbosity
<= s_verbosity
) {
428 fprintf(stderr
, "ps: ");
429 vfprintf(stderr
, fmt
, args
);
433 void PSInstanceTrace(const char* fmt
, ...) {
436 VALog(PSV_TRACE
, fmt
, ap
);
440 void PSInstanceLog(const char* fmt
, ...) {
443 VALog(PSV_LOG
, fmt
, ap
);
447 void PSInstanceWarn(const char* fmt
, ...) {
450 VALog(PSV_WARN
, fmt
, ap
);
454 void PSInstanceError(const char* fmt
, ...) {
457 VALog(PSV_ERROR
, fmt
, ap
);
461 const void* PSGetInterfaceImplementation(const char* interface_name
) {
462 if (strcmp(interface_name
, PPP_INSTANCE_INTERFACE_1_1
) == 0) {
463 static struct PPP_Instance_1_1 interface
= {
465 &Instance_DidDestroy
,
466 &Instance_DidChangeView
,
467 &Instance_DidChangeFocus
,
468 &Instance_HandleDocumentLoad
,
471 } else if (strcmp(interface_name
, PPP_MESSAGING_INTERFACE_1_0
) == 0) {
472 static struct PPP_Messaging_1_0 interface
= {
473 &Messaging_HandleMessage
,
476 } else if (strcmp(interface_name
, PPP_INPUT_EVENT_INTERFACE_0_1
) == 0) {
477 static struct PPP_InputEvent_0_1 interface
= {
478 &InputEvent_HandleInputEvent
,
481 } else if (strcmp(interface_name
, PPP_MOUSELOCK_INTERFACE_1_0
) == 0) {
482 static struct PPP_MouseLock_1_0 interface
= {
483 &MouseLock_MouseLockLost
,
486 } else if (strcmp(interface_name
, PPP_GRAPHICS_3D_INTERFACE_1_0
) == 0) {
487 static struct PPP_Graphics3D_1_0 interface
= {
488 &Graphics3D_Graphics3DContextLost
,