blob: a4b0d41286263a8039a192e354cccc219831927a [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/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <sstream>
#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_);
bool should_exit = si->inst_->exit_message_ == NULL;
if (si->inst_->exit_message_) {
// Send the exit message to JavaScript. Don't call exit(), so the message
// doesn't get dropped.
si->inst_->Log("Posting exit message to JavaScript.\n");
std::stringstream ss;
ss << si->inst_->exit_message_ << ":" << ret;
si->inst_->PostMessage(ss.str());
}
// Clean up StartInfo.
for (uint32_t i = 0; i < si->argc_; i++) {
delete[] si->argv_[i];
}
delete[] si->argv_;
delete si;
if (should_exit) {
// Exit the entire process once the 'main' thread returns.
// The error code will be available to javascript via
// the exitcode parameter 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)
: pp::Instance(instance),
pp::MouseLock(this),
pp::Graphics3DClient(this),
main_loop_(NULL),
events_enabled_(PSE_NONE),
verbosity_(PSV_WARN),
tty_fd_(-1),
tty_prefix_(NULL),
exit_message_(NULL) {
// Set the single Instance object
s_InstanceObject = this;
#ifdef NACL_SDK_DEBUG
SetVerbosity(PSV_LOG);
#endif
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_ = 0;
si->argv_ = new char *[arg+1];
si->argv_[0] = NULL;
// Process embed attributes into the environment.
// Converted the attribute names to uppercase as environment variables are
// case sensitive but are almost universally uppercase in practice.
for (uint32_t i = 0; i < arg; i++) {
std::string key = argn[i];
std::transform(key.begin(), key.end(), key.begin(), toupper);
setenv(key.c_str(), argv[i], 1);
}
// Set a default value for SRC.
setenv("SRC", "NMF?", 0);
// Use the src tag name if ARG0 is not explicitly specified.
setenv("ARG0", getenv("SRC"), 0);
// Walk ARG0..ARGn populating argv until an argument is missing.
for (;;) {
std::ostringstream arg_stream;
arg_stream << "ARG" << si->argc_;
std::string arg_name = arg_stream.str();
const char* next_arg = getenv(arg_name.c_str());
if (NULL == next_arg)
break;
char* value = new char[strlen(next_arg) + 1];
strcpy(value, next_arg);
si->argv_[si->argc_++] = value;
}
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;
}
// Processes the properties set at compile time via the
// initialization macro, or via dynamically set embed attributes
// through instance DidCreate.
bool PSInstance::ProcessProperties() {
// Set default values
setenv("PS_STDIN", "/dev/stdin", 0);
setenv("PS_STDOUT", "/dev/stdout", 0);
setenv("PS_STDERR", "/dev/console3", 0);
// Reset verbosity if passed in
const char* verbosity = getenv("PS_VERBOSITY");
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(getenv("PS_STDIN"), O_RDONLY);
dup2(fd0, 0);
int fd1 = open(getenv("PS_STDOUT"), O_WRONLY);
dup2(fd1, 1);
int fd2 = open(getenv("PS_STDERR"), O_WRONLY);
dup2(fd2, 2);
tty_prefix_ = getenv("PS_TTY_PREFIX");
if (tty_prefix_) {
tty_fd_ = open("/dev/tty", O_WRONLY);
if (tty_fd_ >= 0) {
RegisterMessageHandler(tty_prefix_, MessageHandlerInputStatic, this);
const char* tty_resize = getenv("PS_TTY_RESIZE");
if (tty_resize)
RegisterMessageHandler(tty_resize, MessageHandlerResizeStatic, this);
char* tty_rows = getenv("PS_TTY_ROWS");
char* tty_cols = getenv("PS_TTY_COLS");
if (tty_rows && tty_cols) {
char* end = tty_rows;
int rows = strtol(tty_rows, &end, 10);
if (*end != '\0' || rows < 0) {
Error("Invalid value for PS_TTY_ROWS: %s", tty_rows);
} else {
end = tty_cols;
int cols = strtol(tty_cols, &end, 10);
if (*end != '\0' || cols < 0)
Error("Invalid value for PS_TTY_COLS: %s", tty_cols);
else
HandleResize(cols, rows);
}
}
else if (tty_rows || tty_cols) {
Error("PS_TTY_ROWS and PS_TTY_COLS must be set together");
}
tioc_nacl_output handler;
handler.handler = TtyOutputHandlerStatic;
handler.user_data = this;
ioctl(tty_fd_, TIOCNACLOUTPUT, reinterpret_cast<char*>(&handler));
} else {
Error("Failed to open /dev/tty.\n");
}
}
exit_message_ = getenv("PS_EXIT_MESSAGE");
// 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;
if (mask == 0) {
static bool warn_once = true;
if (warn_once) {
Warn("PSInstance::SetEnabledEvents(mask) where mask == 0 will block\n");
Warn("all events. This can come from PSEventSetFilter(PSE_NONE);\n");
warn_once = false;
}
}
}
void PSInstance::PostEvent(PSEventType type) {
assert(PSE_GRAPHICS3D_GRAPHICS3DCONTEXTLOST == type ||
PSE_MOUSELOCK_MOUSELOCKLOST == 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);
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 (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);
}
ssize_t PSInstance::TtyOutputHandler(const char* buf, size_t count) {
// We prepend the prefix_ to the data in buf, then package it up
// and post it as a message to javascript.
const char* data = static_cast<const char*>(buf);
std::string message = tty_prefix_;
message.append(data, count);
PostMessage(pp::Var(message));
return count;
}
void PSInstance::MessageHandlerInput(const pp::Var& message) {
// 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.
assert(message.is_string());
std::string buffer = message.AsString();
struct tioc_nacl_input_string ioctl_message;
ioctl_message.length = buffer.size();
ioctl_message.buffer = buffer.c_str();
int ret =
ioctl(tty_fd_, TIOCNACLINPUT, reinterpret_cast<char*>(&ioctl_message));
if (ret != 0 && errno != ENOTTY) {
Error("ioctl returned unexpected error: %d.\n", ret);
}
}
void PSInstance::HandleResize(int width, int height){
struct winsize size;
memset(&size, 0, sizeof(size));
size.ws_col = width;
size.ws_row = height;
ioctl(tty_fd_, TIOCSWINSZ, reinterpret_cast<char*>(&size));
}
void PSInstance::MessageHandlerResize(const pp::Var& message) {
assert(message.is_array());
pp::VarArray array(message);
assert(array.GetLength() == 2);
int width = array.Get(0).AsInt();
int height = array.Get(1).AsInt();
HandleResize(width, height);
}
ssize_t PSInstance::TtyOutputHandlerStatic(const char* buf,
size_t count,
void* user_data) {
PSInstance* instance = reinterpret_cast<PSInstance*>(user_data);
return instance->TtyOutputHandler(buf, count);
}
void PSInstance::MessageHandlerInputStatic(const pp::Var& key,
const pp::Var& value,
void* user_data) {
PSInstance* instance = reinterpret_cast<PSInstance*>(user_data);
instance->MessageHandlerInput(value);
}
void PSInstance::MessageHandlerResizeStatic(const pp::Var& key,
const pp::Var& value,
void* user_data) {
PSInstance* instance = reinterpret_cast<PSInstance*>(user_data);
instance->MessageHandlerResize(value);
}
void PSInstance::RegisterMessageHandler(std::string message_name,
MessageHandler_t handler,
void* user_data) {
if (handler == NULL) {
message_handlers_.erase(message_name);
return;
}
MessageHandler message_handler = { handler, user_data };
message_handlers_[message_name] = message_handler;
}
void PSInstance::PostEvent(PSEventType type, const PP_Var& var) {
assert(PSE_INSTANCE_HANDLEMESSAGE == type);
// If the user has specified a tty_prefix_, then filter out the
// matching message here and pass them to the tty node via
// ioctl() rather then adding them to the event queue.
pp::Var event(var);
if (tty_fd_ >= 0 && event.is_string()) {
std::string message = event.AsString();
size_t prefix_len = strlen(tty_prefix_);
if (message.size() > prefix_len) {
if (!strncmp(message.c_str(), tty_prefix_, prefix_len)) {
MessageHandlerInput(pp::Var(message.substr(prefix_len)));
return;
}
}
}
// If the message is a dictionary then see if it matches one
// of the specific handlers, then call that handler rather than
// queuing an event.
if (tty_fd_ >= 0 && event.is_dictionary()) {
pp::VarDictionary dictionary(var);
pp::VarArray keys = dictionary.GetKeys();
if (keys.GetLength() == 1) {
pp::Var key = keys.Get(0);
MessageHandlerMap::iterator iter =
message_handlers_.find(key.AsString());
if (iter != message_handlers_.end()) {
MessageHandler_t handler = iter->second.handler;
void* user_data = iter->second.user_data;
handler(key, dictionary.Get(key), user_data);
return;
}
}
}
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() {
PSEvent* event;
while(true) {
event = event_queue_.Dequeue(false);
if (NULL == event)
break;
if (events_enabled_ & event->type)
break;
// Release filtered events & continue to acquire.
ReleaseEvent(event);
}
return event;
}
PSEvent* PSInstance::WaitAcquireEvent() {
PSEvent* event;
while(true) {
event = event_queue_.Dequeue(true);
if (events_enabled_ & event->type)
break;
// Release filtered events & continue to acquire.
ReleaseEvent(event);
}
return event;
}
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);
}