root/native_client_sdk/src/libraries/ppapi_simple/ps_instance.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. GetInstance
  2. MainThreadThunk
  3. ExitHandshake
  4. MainThread
  5. exit_message_
  6. SetMain
  7. Init
  8. ProcessProperties
  9. SetVerbosity
  10. VALog
  11. Trace
  12. Log
  13. Warn
  14. Error
  15. SetEnabledEvents
  16. PostEvent
  17. PostEvent
  18. PostEvent
  19. TtyOutputHandler
  20. MessageHandlerExit
  21. MessageHandlerInput
  22. HandleExitStatic
  23. HandleResize
  24. MessageHandlerResize
  25. TtyOutputHandlerStatic
  26. MessageHandlerExitStatic
  27. MessageHandlerInputStatic
  28. MessageHandlerResizeStatic
  29. RegisterMessageHandler
  30. PostEvent
  31. TryAcquireEvent
  32. WaitAcquireEvent
  33. ReleaseEvent
  34. HandleMessage
  35. HandleInputEvent
  36. DidChangeView
  37. DidChangeFocus
  38. Graphics3DContextLost
  39. MouseLockLost

// 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 <termios.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);
  PSInstance* instance = si->inst_;
  instance->main_loop_ = new pp::MessageLoop(si->inst_);
  instance->main_loop_->AttachToCurrentThread();

  int ret = instance->MainThread(si->argc_, si->argv_);

  // Clean up StartInfo.
  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 parameter of the crash event.
#ifdef __native_client__
  exit(ret);
#else
  instance->ExitHandshake(ret);
#endif
  return NULL;
}

void PSInstance::ExitHandshake(int status) {
  if (exit_message_ == NULL)
    return;

  RegisterMessageHandler(exit_message_, MessageHandlerExitStatic, this);

  // Send the exit message to JavaScript. Then wait
  // for the reply/confirmation.
  std::stringstream ss;
  ss << exit_message_ << ":" << status;

  pthread_mutex_lock(&exit_lock_);
  PostMessage(ss.str());
  pthread_cond_wait(&exit_cond_, &exit_lock_);
  pthread_mutex_unlock(&exit_lock_);
}

// 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) {

  pthread_mutex_init(&exit_lock_, NULL);
  pthread_cond_init(&exit_cond_, 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() {
  s_InstanceObject = NULL;
}

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() {
  // 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);

  // Set default values
  setenv("PS_STDIN", "/dev/stdin", 0);
  setenv("PS_STDOUT", "/dev/stdout", 0);
  setenv("PS_STDERR", "/dev/console3", 0);

  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");

  // If PS_EXIT_MESSAGE is set in the envionment then we perform a handshake
  // with JavaScript when program exits.
  if (exit_message_ != NULL)
    nacl_io_register_exit_handler(HandleExitStatic, this);

  // 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::MessageHandlerExit(const pp::Var& message) {
  assert(message.is_string());
  pthread_mutex_lock(&exit_lock_);
  pthread_cond_signal(&exit_cond_);
  pthread_mutex_unlock(&exit_lock_);
}

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::HandleExitStatic(int status, void* user_data) {
  PSInstance* instance = reinterpret_cast<PSInstance*>(user_data);
  instance->ExitHandshake(status);
}

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::MessageHandlerExitStatic(const pp::Var& key,
                                          const pp::Var& value,
                                          void* user_data) {
  PSInstance* instance = reinterpret_cast<PSInstance*>(user_data);
  instance->MessageHandlerExit(value);
}

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 (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);
}

/* [<][>][^][v][top][bottom][index][help] */