blob: 741697c452210039a760aa93175d6c5e638843d1 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <cstdlib>
#include <cstring>
#include <map>
#include <string>
#include <vector>
#include "nacl_io/ioctl.h"
#include "nacl_io/kernel_wrap.h"
#include "nacl_io/nacl_io.h"
#include "ppapi/c/ppb_var.h"
#include "ppapi/cpp/input_event.h"
#include "ppapi/cpp/message_loop.h"
#include "ppapi/cpp/module.h"
#include "ppapi/cpp/rect.h"
#include "ppapi/cpp/size.h"
#include "ppapi/cpp/touch_point.h"
#include "ppapi/cpp/var.h"
#include "ppapi_simple/ps_event.h"
#include "ppapi_simple/ps_instance.h"
#include "ppapi_simple/ps_interface.h"
#include "ppapi_simple/ps_main.h"
#if defined(WIN32)
#define open _open
#define dup2 _dup2
#endif
static PSInstance* s_InstanceObject = NULL;
PSInstance* PSInstance::GetInstance() {
return s_InstanceObject;
}
struct StartInfo {
PSInstance* inst_;
uint32_t argc_;
char** argv_;
};
// The starting point for 'main'. We create this thread to hide the real
// main pepper thread which must never be blocked.
void* PSInstance::MainThreadThunk(void *info) {
s_InstanceObject->Trace("Got MainThreadThunk.\n");
StartInfo* si = static_cast<StartInfo*>(info);
si->inst_->main_loop_ = new pp::MessageLoop(si->inst_);
si->inst_->main_loop_->AttachToCurrentThread();
int ret = si->inst_->MainThread(si->argc_, si->argv_);
for (uint32_t i = 0; i < si->argc_; i++) {
delete[] si->argv_[i];
}
delete[] si->argv_;
delete si;
// Exit the entire process once the 'main' thread returns.
// The error code will be available to javascript via
// the exitcode paramater of the crash event.
exit(ret);
return NULL;
}
// The default implementation supports running a 'C' main.
int PSInstance::MainThread(int argc, char *argv[]) {
if (!main_cb_) {
Error("No main defined.\n");
return 0;
}
Trace("Starting MAIN.\n");
int ret = main_cb_(argc, argv);
Log("Main thread returned with %d.\n", ret);
return ret;
}
PSInstance::PSInstance(PP_Instance instance, const char *argv[])
: pp::Instance(instance),
pp::MouseLock(this),
pp::Graphics3DClient(this),
main_loop_(NULL),
events_enabled_(PSE_NONE),
verbosity_(PSV_WARN),
fd_tty_(-1) {
// Set the single Instance object
s_InstanceObject = this;
#ifdef NACL_SDK_DEBUG
SetVerbosity(PSV_LOG);
#endif
// Place PPAPI_MAIN_USE arguments into properties map
while (*argv) {
std::string key = *argv++;
std::string val = *argv++;
properties_[key] = val;
}
RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE |
PP_INPUTEVENT_CLASS_KEYBOARD |
PP_INPUTEVENT_CLASS_WHEEL |
PP_INPUTEVENT_CLASS_TOUCH);
}
PSInstance::~PSInstance() {}
void PSInstance::SetMain(PSMainFunc_t main) {
main_cb_ = main;
}
bool PSInstance::Init(uint32_t arg,
const char* argn[],
const char* argv[]) {
StartInfo* si = new StartInfo;
si->inst_ = this;
si->argc_ = 1;
si->argv_ = new char *[arg*2+1];
si->argv_[0] = NULL;
// Process arguments passed into Module INIT from JavaScript
for (uint32_t i = 0; i < arg; i++) {
// If we start with PM prefix set the instance argument map
if (0 == strncmp(argn[i], "ps_", 3)) {
std::string key = argn[i];
std::string val = argv[i];
properties_[key] = val;
continue;
}
// Chrome passed @dev as an internal extra attribute in
// some cases. Ignore this.
if (0 == strcmp(argn[i], "@dev")) {
continue;
}
// If this is the 'src' tag, then get the NMF name.
if (!strcmp("src", argn[i])) {
char *name = new char[strlen(argv[i]) + 1];
strcpy(name, argv[i]);
si->argv_[0] = name;
} else {
// Otherwise turn html tag attributes into arguments
// that get passed to the main funciton. Attributes
// without values get transformed into "--name" and
// attributes with values become "--name=value".
int arglen = strlen(argn[i]) + 3;
if (argv[i] && argv[i][0])
arglen += strlen(argv[i]) + 1;
char* arg = new char[arglen];
if (argv[i] && argv[i][0])
sprintf(arg, "--%s=%s", argn[i], argv[i]);
else
sprintf(arg, "--%s", argn[i]);
si->argv_[si->argc_++] = arg;
}
}
// If src was not found, set the first arg to something
if (NULL == si->argv_[0]) {
char *name = new char[5];
strcpy(name, "NMF?");
si->argv_[0] = name;
}
PSInterfaceInit();
bool props_processed = ProcessProperties();
// Log arg values only once ProcessProperties has been
// called so that the ps_verbosity attribute will be in
// effect.
for (uint32_t i = 0; i < arg; i++) {
if (argv[i]) {
Trace("attribs[%d] '%s=%s'\n", i, argn[i], argv[i]);
} else {
Trace("attribs[%d] '%s'\n", i, argn[i]);
}
}
for (uint32_t i = 0; i < si->argc_; i++) {
Trace("argv[%d] '%s'\n", i, si->argv_[i]);
}
if (!props_processed) {
Warn("Skipping create thread.\n");
return false;
}
pthread_t main_thread;
int ret = pthread_create(&main_thread, NULL, MainThreadThunk, si);
Trace("Created thread: %d.\n", ret);
return ret == 0;
}
const char* PSInstance::GetProperty(const char* key, const char* def) {
PropertyMap_t::iterator it = properties_.find(key);
if (it != properties_.end()) {
return it->second.c_str();
}
return def;
}
// Processes the properties set at compile time via the
// initialization macro, or via dynamically set embed attributes
// through instance DidCreate.
bool PSInstance::ProcessProperties() {
// Get the default values
const char* stdin_path = GetProperty("ps_stdin", "/dev/stdin");
const char* stdout_path = GetProperty("ps_stdout", "/dev/stdout");
const char* stderr_path = GetProperty("ps_stderr", "/dev/console3");
const char* verbosity = GetProperty("ps_verbosity", NULL);
const char* tty_prefix = GetProperty("ps_tty_prefix", NULL);
// Reset verbosity if passed in
if (verbosity) SetVerbosity(static_cast<Verbosity>(atoi(verbosity)));
// Enable NaCl IO to map STDIN, STDOUT, and STDERR
nacl_io_init_ppapi(PSGetInstanceId(), PSGetInterface);
int fd0 = open(stdin_path, O_RDONLY);
dup2(fd0, 0);
int fd1 = open(stdout_path, O_WRONLY);
dup2(fd1, 1);
int fd2 = open(stderr_path, O_WRONLY);
dup2(fd2, 2);
if (tty_prefix) {
fd_tty_ = open("/dev/tty", O_WRONLY);
if (fd_tty_ >= 0) {
ioctl(fd_tty_, TIOCNACLPREFIX, const_cast<char*>(tty_prefix));
} else {
Error("Failed to open /dev/tty.\n");
}
}
// Set line buffering on stdout and stderr
#if !defined(WIN32)
setvbuf(stderr, NULL, _IOLBF, 0);
setvbuf(stdout, NULL, _IOLBF, 0);
#endif
return true;
}
void PSInstance::SetVerbosity(Verbosity verbosity) {
verbosity_ = verbosity;
}
void PSInstance::VALog(Verbosity verbosity, const char *fmt, va_list args) {
if (verbosity <= verbosity_) {
fprintf(stderr, "ps: ");
vfprintf(stderr, fmt, args);
}
}
void PSInstance::Trace(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
VALog(PSV_TRACE, fmt, ap);
va_end(ap);
}
void PSInstance::Log(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
VALog(PSV_LOG, fmt, ap);
va_end(ap);
}
void PSInstance::Warn(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
VALog(PSV_WARN, fmt, ap);
va_end(ap);
}
void PSInstance::Error(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
VALog(PSV_ERROR, fmt, ap);
va_end(ap);
}
void PSInstance::SetEnabledEvents(uint32_t mask) {
events_enabled_ = mask;
}
void PSInstance::PostEvent(PSEventType type) {
assert(PSE_GRAPHICS3D_GRAPHICS3DCONTEXTLOST == type ||
PSE_MOUSELOCK_MOUSELOCKLOST == type);
if (events_enabled_ & type) {
PSEvent *env = (PSEvent *) malloc(sizeof(PSEvent));
memset(env, 0, sizeof(*env));
env->type = type;
event_queue_.Enqueue(env);
}
}
void PSInstance::PostEvent(PSEventType type, PP_Bool bool_value) {
assert(PSE_INSTANCE_DIDCHANGEFOCUS == type);
if (events_enabled_ & type) {
PSEvent *env = (PSEvent *) malloc(sizeof(PSEvent));
memset(env, 0, sizeof(*env));
env->type = type;
env->as_bool = bool_value;
event_queue_.Enqueue(env);
}
}
void PSInstance::PostEvent(PSEventType type, PP_Resource resource) {
assert(PSE_INSTANCE_HANDLEINPUT == type ||
PSE_INSTANCE_DIDCHANGEVIEW == type);
if (events_enabled_ & type) {
if (resource) {
PSInterfaceCore()->AddRefResource(resource);
}
PSEvent *env = (PSEvent *) malloc(sizeof(PSEvent));
memset(env, 0, sizeof(*env));
env->type = type;
env->as_resource = resource;
event_queue_.Enqueue(env);
}
}
void PSInstance::PostEvent(PSEventType type, const PP_Var& var) {
assert(PSE_INSTANCE_HANDLEMESSAGE == type);
// If the user has specified a tty_prefix_ (using ioctl), then we'll give the
// tty node a chance to vacuum up any messages beginning with that prefix. If
// the message does not start with the prefix, the ioctl call will return
// ENOENT and we'll pass the message through to the event queue.
if (fd_tty_ >= 0 && var.type == PP_VARTYPE_STRING) {
uint32_t message_len;
const char* message = PSInterfaceVar()->VarToUtf8(var, &message_len);
std::string message_str(message, message + message_len);
// Since our message may contain null characters, we can't send it as a
// naked C string, so we package it up in this struct before sending it
// to the ioctl.
struct tioc_nacl_input_string ioctl_message;
ioctl_message.length = message_len;
ioctl_message.buffer = message_str.data();
int ret =
ioctl(fd_tty_, TIOCNACLINPUT, reinterpret_cast<char*>(&ioctl_message));
if (ret != 0 && errno != ENOTTY) {
Error("ioctl returned unexpected error: %d.\n", ret);
}
return;
}
if (events_enabled_ & type) {
PSInterfaceVar()->AddRef(var);
PSEvent *env = (PSEvent *) malloc(sizeof(PSEvent));
memset(env, 0, sizeof(*env));
env->type = type;
env->as_var = var;
event_queue_.Enqueue(env);
}
}
PSEvent* PSInstance::TryAcquireEvent() {
return event_queue_.Dequeue(false);
}
PSEvent* PSInstance::WaitAcquireEvent() {
return event_queue_.Dequeue(true);
}
void PSInstance::ReleaseEvent(PSEvent* event) {
if (event) {
switch(event->type) {
case PSE_INSTANCE_HANDLEMESSAGE:
PSInterfaceVar()->Release(event->as_var);
break;
case PSE_INSTANCE_HANDLEINPUT:
case PSE_INSTANCE_DIDCHANGEVIEW:
if (event->as_resource) {
PSInterfaceCore()->ReleaseResource(event->as_resource);
}
break;
default:
break;
}
free(event);
}
}
void PSInstance::HandleMessage(const pp::Var& message) {
Trace("Got Message\n");
PostEvent(PSE_INSTANCE_HANDLEMESSAGE, message.pp_var());
}
bool PSInstance::HandleInputEvent(const pp::InputEvent& event) {
PostEvent(PSE_INSTANCE_HANDLEINPUT, event.pp_resource());
return true;
}
void PSInstance::DidChangeView(const pp::View& view) {
pp::Size new_size = view.GetRect().size();
Log("Got View change: %d,%d\n", new_size.width(), new_size.height());
PostEvent(PSE_INSTANCE_DIDCHANGEVIEW, view.pp_resource());
}
void PSInstance::DidChangeFocus(bool focus) {
Log("Got Focus change: %s\n", focus ? "FOCUS ON" : "FOCUS OFF");
PostEvent(PSE_INSTANCE_DIDCHANGEFOCUS, focus ? PP_TRUE : PP_FALSE);
}
void PSInstance::Graphics3DContextLost() {
Log("Graphics3DContextLost\n");
PostEvent(PSE_GRAPHICS3D_GRAPHICS3DCONTEXTLOST);
}
void PSInstance::MouseLockLost() {
Log("MouseLockLost\n");
PostEvent(PSE_MOUSELOCK_MOUSELOCKLOST);
}