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_main.h"
44 PP_Instance g_ps_instance
;
45 PPB_GetInterface g_ps_get_interface
;
46 PSMainFunc_t g_ps_main_cb
;
48 static enum PSVerbosity s_verbosity
;
52 static const char* s_tty_prefix
;
54 /* Condition variable and lock used to wait for exit confirmation from
56 static pthread_cond_t s_exit_cond
;
57 static pthread_mutex_t s_exit_lock
;
59 /* A message to Post to JavaScript instead of exiting, or NULL if exit() should
60 * be called instead. */
61 static char* s_exit_message
;
63 static int ProcessProperties(void);
64 ssize_t
TtyOutputHandler(const char* buf
, size_t count
, void* user_data
);
65 static void MessageHandlerExit(struct PP_Var key
,
68 static void MessageHandlerInput(struct PP_Var key
,
71 static void MessageHandlerResize(struct PP_Var key
,
74 static void HandleResize(int width
, int height
);
75 static void* MainThread(void* info
);
76 static void ExitHandshake(int status
, void* user_data
);
78 static void PostMessageString(const char* message
) {
79 struct PP_Var message_var
=
80 PSInterfaceVar()->VarFromUtf8(message
, strlen(message
));
81 PSInterfaceMessaging()->PostMessage(g_ps_instance
, message_var
);
82 PSInterfaceVar()->Release(message_var
);
85 static PP_Bool
Instance_DidCreate(PP_Instance instance
,
89 g_ps_instance
= instance
;
90 g_ps_main_cb
= PSUserMainGet();
91 s_verbosity
= PSV_LOG
;
92 PSInterfaceInputEvent()->RequestInputEvents(
93 g_ps_instance
, PP_INPUTEVENT_CLASS_MOUSE
| PP_INPUTEVENT_CLASS_KEYBOARD
|
94 PP_INPUTEVENT_CLASS_WHEEL
| PP_INPUTEVENT_CLASS_TOUCH
);
97 struct StartInfo
* si
= malloc(sizeof(struct StartInfo
));
100 si
->argv_
= calloc(argc
+ 1, sizeof(char*));
103 /* Process embed attributes into the environment.
104 * Convert the attribute names to uppercase as environment variables are case
105 * sensitive but are almost universally uppercase in practice. */
106 for (i
= 0; i
< argc
; i
++) {
107 char* key
= strdup(argn
[i
]);
110 *c
= toupper((int)*c
);
113 setenv(key
, argv
[i
], 1);
117 /* Set a default value for SRC. */
118 setenv("SRC", "NMF?", 0);
119 /* Use the src tag name if ARG0 is not explicitly specified. */
120 setenv("ARG0", getenv("SRC"), 0);
122 /* Walk ARG0..ARGn populating argv until an argument is missing. */
125 snprintf(arg_name
, 32, "ARG%d", si
->argc_
);
126 const char* next_arg
= getenv(arg_name
);
127 if (NULL
== next_arg
)
130 si
->argv_
[si
->argc_
++] = strdup(next_arg
);
133 int props_processed
= ProcessProperties();
135 /* Log arg values only once ProcessProperties has been called so that the
136 * PS_VERBOSITY attribute will be in effect. */
137 for (i
= 0; i
< argc
; i
++) {
139 PSInstanceTrace("attribs[%d] '%s=%s'\n", i
, argn
[i
], argv
[i
]);
141 PSInstanceTrace("attribs[%d] '%s'\n", i
, argn
[i
]);
145 for (i
= 0; i
< si
->argc_
; i
++) {
146 PSInstanceTrace("argv[%d] '%s'\n", i
, si
->argv_
[i
]);
149 if (!props_processed
) {
150 PSInstanceWarn("Skipping create thread.\n");
154 pthread_t main_thread
;
155 int ret
= pthread_create(&main_thread
, NULL
, MainThread
, si
);
156 PSInstanceTrace("Created thread: %d.\n", ret
);
157 return ret
== 0 ? PP_TRUE
: PP_FALSE
;
160 int ProcessProperties(void) {
161 /* Reset verbosity if passed in */
162 const char* verbosity
= getenv("PS_VERBOSITY");
164 PSInstanceSetVerbosity(atoi(verbosity
));
166 /* Enable NaCl IO to map STDIN, STDOUT, and STDERR */
167 nacl_io_init_ppapi(PSGetInstanceId(), PSGetInterface
);
169 s_tty_prefix
= getenv("PS_TTY_PREFIX");
171 s_tty_fd
= open("/dev/tty", O_WRONLY
);
173 PSEventRegisterMessageHandler(s_tty_prefix
, MessageHandlerInput
, NULL
);
174 const char* tty_resize
= getenv("PS_TTY_RESIZE");
176 PSEventRegisterMessageHandler(tty_resize
, MessageHandlerResize
, NULL
);
178 char* tty_rows
= getenv("PS_TTY_ROWS");
179 char* tty_cols
= getenv("PS_TTY_COLS");
180 if (tty_rows
&& tty_cols
) {
181 char* end
= tty_rows
;
182 int rows
= strtol(tty_rows
, &end
, 10);
183 if (*end
!= '\0' || rows
< 0) {
184 PSInstanceError("Invalid value for PS_TTY_ROWS: %s\n", tty_rows
);
187 int cols
= strtol(tty_cols
, &end
, 10);
188 if (*end
!= '\0' || cols
< 0)
189 PSInstanceError("Invalid value for PS_TTY_COLS: %s\n", tty_cols
);
191 HandleResize(cols
, rows
);
193 } else if (tty_rows
|| tty_cols
) {
194 PSInstanceError("PS_TTY_ROWS and PS_TTY_COLS must be set together\n");
197 struct tioc_nacl_output handler
;
198 handler
.handler
= TtyOutputHandler
;
199 handler
.user_data
= NULL
;
200 ioctl(s_tty_fd
, TIOCNACLOUTPUT
, &handler
);
202 PSInstanceError("Failed to open /dev/tty.\n");
206 /* Set default values */
207 setenv("PS_STDIN", "/dev/stdin", 0);
208 setenv("PS_STDOUT", "/dev/stdout", 0);
209 setenv("PS_STDERR", "/dev/console3", 0);
211 int fd0
= open(getenv("PS_STDIN"), O_RDONLY
);
214 int fd1
= open(getenv("PS_STDOUT"), O_WRONLY
);
217 int fd2
= open(getenv("PS_STDERR"), O_WRONLY
);
220 PSEventRegisterMessageHandler("jspipe1", MessageHandlerInput
, NULL
);
221 PSEventRegisterMessageHandler("jspipe2", MessageHandlerInput
, NULL
);
222 PSEventRegisterMessageHandler("jspipe3", MessageHandlerInput
, NULL
);
224 s_exit_message
= getenv("PS_EXIT_MESSAGE");
226 /* If PS_EXIT_MESSAGE is set in the environment then we perform a handshake
227 * with JavaScript when program exits. */
228 if (s_exit_message
!= NULL
)
229 nacl_io_set_exit_callback(ExitHandshake
, NULL
);
231 /* Set line buffering on stdout and stderr */
233 setvbuf(stderr
, NULL
, _IOLBF
, 0);
234 setvbuf(stdout
, NULL
, _IOLBF
, 0);
239 ssize_t
TtyOutputHandler(const char* data
, size_t count
, void* user_data
) {
240 /* We prepend the s_tty_prefix to the data in buf, then package it up and
241 * post it as a message to javascript. */
242 size_t tty_prefix_len
= strlen(s_tty_prefix
);
243 char* message
= alloca(tty_prefix_len
+ count
+ 1);
244 memcpy(message
, s_tty_prefix
, tty_prefix_len
);
245 memcpy(message
+ tty_prefix_len
, data
, count
);
246 message
[tty_prefix_len
+ count
] = 0;
247 PostMessageString(message
);
251 void MessageHandlerExit(struct PP_Var key
,
254 pthread_mutex_lock(&s_exit_lock
);
255 pthread_cond_signal(&s_exit_cond
);
256 pthread_mutex_unlock(&s_exit_lock
);
259 void MessageHandlerInput(struct PP_Var key
,
263 const char* key_str
= PSInterfaceVar()->VarToUtf8(key
, &key_len
);
265 const char* filename
= NULL
;
266 if (strncmp(key_str
, s_tty_prefix
, key_len
) == 0) {
267 filename
= "/dev/tty";
268 } else if (strncmp(key_str
, "jspipe1", key_len
) == 0) {
269 filename
= "/dev/jspipe1";
270 } else if (strncmp(key_str
, "jspipe2", key_len
) == 0) {
271 filename
= "/dev/jspipe2";
272 } else if (strncmp(key_str
, "jspipe3", key_len
) == 0) {
273 filename
= "/dev/jspipe3";
275 PSInstanceError("unexpected input key: %s", key_str
);
279 int fd
= open(filename
, O_RDONLY
);
281 PSInstanceError("error opening file: %s (%s)", filename
, strerror(errno
));
285 int ret
= ioctl(fd
, NACL_IOC_HANDLEMESSAGE
, &value
);
287 PSInstanceError("ioctl on %s failed: %d.\n", filename
, ret
);
295 void MessageHandlerResize(struct PP_Var key
,
298 assert(value
.type
== PP_VARTYPE_ARRAY
);
299 assert(PSInterfaceVarArray()->GetLength(value
) == 2);
301 struct PP_Var width_var
= PSInterfaceVarArray()->Get(value
, 0);
302 struct PP_Var height_var
= PSInterfaceVarArray()->Get(value
, 1);
304 assert(width_var
.type
== PP_VARTYPE_INT32
);
305 assert(height_var
.type
== PP_VARTYPE_INT32
);
307 int width
= width_var
.value
.as_int
;
308 int height
= height_var
.value
.as_int
;
310 HandleResize(width
, height
);
313 void HandleResize(int width
, int height
) {
315 memset(&size
, 0, sizeof(size
));
317 size
.ws_row
= height
;
318 ioctl(s_tty_fd
, TIOCSWINSZ
, &size
);
321 void* MainThread(void* info
) {
324 PSInstanceTrace("Running MainThread.\n");
325 struct StartInfo
* si
= (struct StartInfo
*)info
;
327 PP_Resource message_loop
= PSInterfaceMessageLoop()->Create(g_ps_instance
);
328 if (PSInterfaceMessageLoop()->AttachToCurrentThread(message_loop
) != PP_OK
) {
329 PSInstanceError("Unable to attach message loop to thread.\n");
334 PSInstanceError("No main defined.\n");
338 PSInstanceTrace("Starting MAIN.\n");
339 ret
= g_ps_main_cb(si
->argc_
, si
->argv_
);
340 PSInstanceLog("Main thread returned with %d.\n", ret
);
342 /* Clean up StartInfo. */
343 for (i
= 0; i
< si
->argc_
; i
++) {
349 /* Exit the entire process once the 'main' thread returns. The error code
350 * will be available to javascript via the exitcode parameter of the crash
352 #ifdef __native_client__
355 ExitHandshake(ret
, NULL
);
360 void ExitHandshake(int status
, void* user_data
) {
361 if (s_exit_message
== NULL
)
364 PSEventRegisterMessageHandler(s_exit_message
, MessageHandlerExit
, NULL
);
366 /* exit message + ':' + num + \0 */
367 size_t message_len
= strlen(s_exit_message
) + 1 + 11 + 1;
368 char* message
= alloca(message_len
);
369 snprintf(message
, message_len
, "%s:%d", s_exit_message
, status
);
371 pthread_mutex_lock(&s_exit_lock
);
372 PostMessageString(message
);
373 pthread_cond_wait(&s_exit_cond
, &s_exit_lock
);
374 pthread_mutex_unlock(&s_exit_lock
);
377 static void Instance_DidDestroy(PP_Instance instance
) {
380 static void Instance_DidChangeView(PP_Instance instance
, PP_Resource view
) {
382 if (PSInterfaceView()->GetRect(view
, &rect
)) {
383 PSInstanceLog("Got View change: %d,%d\n", rect
.size
.width
,
385 PSEventPostResource(PSE_INSTANCE_DIDCHANGEVIEW
, view
);
389 static void Instance_DidChangeFocus(PP_Instance instance
, PP_Bool has_focus
) {
390 PSInstanceLog("Got Focus change: %s\n", has_focus
? "FOCUS ON" : "FOCUS OFF");
391 PSEventPostBool(PSE_INSTANCE_DIDCHANGEFOCUS
, has_focus
? PP_TRUE
: PP_FALSE
);
394 static PP_Bool
Instance_HandleDocumentLoad(PP_Instance instance
,
395 PP_Resource url_loader
) {
399 static void Messaging_HandleMessage(PP_Instance instance
,
400 struct PP_Var message
) {
401 PSInstanceTrace("Got Message\n");
402 PSEventPostVar(PSE_INSTANCE_HANDLEMESSAGE
, message
);
405 static PP_Bool
InputEvent_HandleInputEvent(PP_Instance instance
,
406 PP_Resource input_event
) {
407 PSEventPostResource(PSE_INSTANCE_HANDLEINPUT
, input_event
);
411 static void MouseLock_MouseLockLost(PP_Instance instance
) {
412 PSInstanceLog("MouseLockLost\n");
413 PSEventPost(PSE_MOUSELOCK_MOUSELOCKLOST
);
416 static void Graphics3D_Graphics3DContextLost(PP_Instance instance
) {
417 PSInstanceLog("Graphics3DContextLost\n");
418 PSEventPost(PSE_GRAPHICS3D_GRAPHICS3DCONTEXTLOST
);
421 void PSInstanceSetVerbosity(enum PSVerbosity verbosity
) {
422 s_verbosity
= verbosity
;
425 static void VALog(enum PSVerbosity verbosity
, const char* fmt
, va_list args
) {
426 if (verbosity
<= s_verbosity
) {
427 fprintf(stderr
, "ps: ");
428 vfprintf(stderr
, fmt
, args
);
432 void PSInstanceTrace(const char* fmt
, ...) {
435 VALog(PSV_TRACE
, fmt
, ap
);
439 void PSInstanceLog(const char* fmt
, ...) {
442 VALog(PSV_LOG
, fmt
, ap
);
446 void PSInstanceWarn(const char* fmt
, ...) {
449 VALog(PSV_WARN
, fmt
, ap
);
453 void PSInstanceError(const char* fmt
, ...) {
456 VALog(PSV_ERROR
, fmt
, ap
);
460 const void* PSGetInterfaceImplementation(const char* interface_name
) {
461 if (strcmp(interface_name
, PPP_INSTANCE_INTERFACE_1_1
) == 0) {
462 static struct PPP_Instance_1_1 interface
= {
464 &Instance_DidDestroy
,
465 &Instance_DidChangeView
,
466 &Instance_DidChangeFocus
,
467 &Instance_HandleDocumentLoad
,
470 } else if (strcmp(interface_name
, PPP_MESSAGING_INTERFACE_1_0
) == 0) {
471 static struct PPP_Messaging_1_0 interface
= {
472 &Messaging_HandleMessage
,
475 } else if (strcmp(interface_name
, PPP_INPUT_EVENT_INTERFACE_0_1
) == 0) {
476 static struct PPP_InputEvent_0_1 interface
= {
477 &InputEvent_HandleInputEvent
,
480 } else if (strcmp(interface_name
, PPP_MOUSELOCK_INTERFACE_1_0
) == 0) {
481 static struct PPP_MouseLock_1_0 interface
= {
482 &MouseLock_MouseLockLost
,
485 } else if (strcmp(interface_name
, PPP_GRAPHICS_3D_INTERFACE_1_0
) == 0) {
486 static struct PPP_Graphics3D_1_0 interface
= {
487 &Graphics3D_Graphics3DContextLost
,