summaryrefslogtreecommitdiffstats
path: root/native_client_sdk/src/examples
diff options
context:
space:
mode:
authorbradnelson@google.com <bradnelson@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2011-11-19 02:46:32 +0000
committerbradnelson@google.com <bradnelson@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2011-11-19 02:46:32 +0000
commit745945afe8e9fbfad7bb9ed442143172bf690073 (patch)
tree770e9c5f61e1204221fb4c2887cb863ad8b6688f /native_client_sdk/src/examples
parentf0953ccbeb01a7a4ed1daa3cb84c2bc42adbed1f (diff)
downloadchromium_src-745945afe8e9fbfad7bb9ed442143172bf690073.zip
chromium_src-745945afe8e9fbfad7bb9ed442143172bf690073.tar.gz
chromium_src-745945afe8e9fbfad7bb9ed442143172bf690073.tar.bz2
Adding reduced version of sdk at 1387 to chrome tree.
BUG=None TEST=None R=noelallen@google.com TBR git-svn-id: svn://svn.chromium.org/chrome/trunk/src@110822 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'native_client_sdk/src/examples')
-rw-r--r--native_client_sdk/src/examples/build.scons82
-rw-r--r--native_client_sdk/src/examples/common/check_browser.js178
-rw-r--r--native_client_sdk/src/examples/favicon.icobin0 -> 1150 bytes
-rw-r--r--native_client_sdk/src/examples/fullscreen_tumbler/bind.js22
-rw-r--r--native_client_sdk/src/examples/fullscreen_tumbler/build.scons66
-rw-r--r--native_client_sdk/src/examples/fullscreen_tumbler/callback.h92
-rw-r--r--native_client_sdk/src/examples/fullscreen_tumbler/cube.cc268
-rw-r--r--native_client_sdk/src/examples/fullscreen_tumbler/cube.h98
-rw-r--r--native_client_sdk/src/examples/fullscreen_tumbler/dragger.js134
-rw-r--r--native_client_sdk/src/examples/fullscreen_tumbler/fullscreen_tumbler.html57
-rw-r--r--native_client_sdk/src/examples/fullscreen_tumbler/opengl_context.cc84
-rw-r--r--native_client_sdk/src/examples/fullscreen_tumbler/opengl_context.h94
-rw-r--r--native_client_sdk/src/examples/fullscreen_tumbler/opengl_context_ptrs.h22
-rw-r--r--native_client_sdk/src/examples/fullscreen_tumbler/scripting_bridge.cc96
-rw-r--r--native_client_sdk/src/examples/fullscreen_tumbler/scripting_bridge.h53
-rw-r--r--native_client_sdk/src/examples/fullscreen_tumbler/shader_util.cc97
-rw-r--r--native_client_sdk/src/examples/fullscreen_tumbler/shader_util.h30
-rw-r--r--native_client_sdk/src/examples/fullscreen_tumbler/trackball.js296
-rw-r--r--native_client_sdk/src/examples/fullscreen_tumbler/transforms.cc117
-rw-r--r--native_client_sdk/src/examples/fullscreen_tumbler/transforms.h45
-rw-r--r--native_client_sdk/src/examples/fullscreen_tumbler/tumbler.cc202
-rw-r--r--native_client_sdk/src/examples/fullscreen_tumbler/tumbler.h83
-rw-r--r--native_client_sdk/src/examples/fullscreen_tumbler/tumbler.js133
-rw-r--r--native_client_sdk/src/examples/fullscreen_tumbler/tumbler_module.cc45
-rw-r--r--native_client_sdk/src/examples/fullscreen_tumbler/vector3.js91
-rw-r--r--native_client_sdk/src/examples/geturl/build.scons41
-rw-r--r--native_client_sdk/src/examples/geturl/geturl.cc96
-rw-r--r--native_client_sdk/src/examples/geturl/geturl.html125
-rw-r--r--native_client_sdk/src/examples/geturl/geturl_handler.cc137
-rw-r--r--native_client_sdk/src/examples/geturl/geturl_handler.h81
-rw-r--r--native_client_sdk/src/examples/geturl/geturl_success.html20
-rw-r--r--native_client_sdk/src/examples/hello_world/build.scons59
-rw-r--r--native_client_sdk/src/examples/hello_world/hello_world.cc130
-rw-r--r--native_client_sdk/src/examples/hello_world/hello_world.html126
-rw-r--r--native_client_sdk/src/examples/hello_world/helper_functions.cc22
-rw-r--r--native_client_sdk/src/examples/hello_world/helper_functions.h34
-rw-r--r--native_client_sdk/src/examples/hello_world/test_helper_functions.cc51
-rw-r--r--native_client_sdk/src/examples/hello_world_c/build.scons40
-rw-r--r--native_client_sdk/src/examples/hello_world_c/hello_world_c.c287
-rw-r--r--native_client_sdk/src/examples/hello_world_c/hello_world_c.html126
-rw-r--r--native_client_sdk/src/examples/hello_world_c/hello_world_c_dbg.html126
-rw-r--r--native_client_sdk/src/examples/httpd.cmd8
-rwxr-xr-xnative_client_sdk/src/examples/httpd.py125
-rw-r--r--native_client_sdk/src/examples/index.html51
-rw-r--r--native_client_sdk/src/examples/index_staging.html113
-rw-r--r--native_client_sdk/src/examples/input_events/build.scons41
-rw-r--r--native_client_sdk/src/examples/input_events/input_events.cc251
-rw-r--r--native_client_sdk/src/examples/input_events/input_events.html58
-rw-r--r--native_client_sdk/src/examples/load_progress/build.scons51
-rw-r--r--native_client_sdk/src/examples/load_progress/load_progress.cc69
-rw-r--r--native_client_sdk/src/examples/load_progress/load_progress.html232
-rw-r--r--native_client_sdk/src/examples/mouselock/build.scons51
-rw-r--r--native_client_sdk/src/examples/mouselock/mouselock.cc329
-rw-r--r--native_client_sdk/src/examples/mouselock/mouselock.h102
-rw-r--r--native_client_sdk/src/examples/mouselock/mouselock.html108
-rw-r--r--native_client_sdk/src/examples/multithreaded_input_events/build.scons41
-rw-r--r--native_client_sdk/src/examples/multithreaded_input_events/custom_events.cc111
-rw-r--r--native_client_sdk/src/examples/multithreaded_input_events/custom_events.h133
-rw-r--r--native_client_sdk/src/examples/multithreaded_input_events/mt_input_events.cc323
-rw-r--r--native_client_sdk/src/examples/multithreaded_input_events/mt_input_events.html80
-rw-r--r--native_client_sdk/src/examples/multithreaded_input_events/shared_queue.h137
-rw-r--r--native_client_sdk/src/examples/multithreaded_input_events/thread_safe_ref_count.h63
-rw-r--r--native_client_sdk/src/examples/pi_generator/build.scons40
-rw-r--r--native_client_sdk/src/examples/pi_generator/pi_generator.cc240
-rw-r--r--native_client_sdk/src/examples/pi_generator/pi_generator.h117
-rw-r--r--native_client_sdk/src/examples/pi_generator/pi_generator.html83
-rw-r--r--native_client_sdk/src/examples/pi_generator/pi_generator_dbg.html83
-rw-r--r--native_client_sdk/src/examples/pi_generator/pi_generator_module.cc34
-rw-r--r--native_client_sdk/src/examples/pong/build.scons40
-rw-r--r--native_client_sdk/src/examples/pong/pong.cc414
-rw-r--r--native_client_sdk/src/examples/pong/pong.h92
-rw-r--r--native_client_sdk/src/examples/pong/pong.html74
-rw-r--r--native_client_sdk/src/examples/pong/pong_module.cc34
-rw-r--r--native_client_sdk/src/examples/pong/view.cc162
-rw-r--r--native_client_sdk/src/examples/pong/view.h75
-rwxr-xr-xnative_client_sdk/src/examples/scons39
-rwxr-xr-xnative_client_sdk/src/examples/scons.bat43
-rw-r--r--native_client_sdk/src/examples/sine_synth/build.scons40
-rw-r--r--native_client_sdk/src/examples/sine_synth/sine_synth.cc183
-rw-r--r--native_client_sdk/src/examples/sine_synth/sine_synth.html80
-rw-r--r--native_client_sdk/src/examples/tumbler/bind.js22
-rw-r--r--native_client_sdk/src/examples/tumbler/build.scons55
-rw-r--r--native_client_sdk/src/examples/tumbler/callback.h92
-rw-r--r--native_client_sdk/src/examples/tumbler/cube.cc267
-rw-r--r--native_client_sdk/src/examples/tumbler/cube.h97
-rw-r--r--native_client_sdk/src/examples/tumbler/dragger.js134
-rw-r--r--native_client_sdk/src/examples/tumbler/opengl_context.cc84
-rw-r--r--native_client_sdk/src/examples/tumbler/opengl_context.h93
-rw-r--r--native_client_sdk/src/examples/tumbler/opengl_context_ptrs.h22
-rw-r--r--native_client_sdk/src/examples/tumbler/scripting_bridge.cc95
-rw-r--r--native_client_sdk/src/examples/tumbler/scripting_bridge.h52
-rw-r--r--native_client_sdk/src/examples/tumbler/shader_util.cc95
-rw-r--r--native_client_sdk/src/examples/tumbler/shader_util.h29
-rw-r--r--native_client_sdk/src/examples/tumbler/trackball.js296
-rw-r--r--native_client_sdk/src/examples/tumbler/transforms.cc116
-rw-r--r--native_client_sdk/src/examples/tumbler/transforms.h45
-rw-r--r--native_client_sdk/src/examples/tumbler/tumbler.cc137
-rw-r--r--native_client_sdk/src/examples/tumbler/tumbler.h64
-rw-r--r--native_client_sdk/src/examples/tumbler/tumbler.html33
-rw-r--r--native_client_sdk/src/examples/tumbler/tumbler.js133
-rw-r--r--native_client_sdk/src/examples/tumbler/tumbler_module.cc45
-rw-r--r--native_client_sdk/src/examples/tumbler/vector3.js91
102 files changed, 10433 insertions, 0 deletions
diff --git a/native_client_sdk/src/examples/build.scons b/native_client_sdk/src/examples/build.scons
new file mode 100644
index 0000000..f804d4b
--- /dev/null
+++ b/native_client_sdk/src/examples/build.scons
@@ -0,0 +1,82 @@
+#! -*- python -*-
+#
+# Copyright (c) 2011 The Native Client Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import os
+import shutil
+import sys
+
+"""
+Build file for the NaCl SDK Examples
+
+This file runs all the scons files in the various example sub-directories.
+Do not invoke this script directly, but instead use the scons or scons.bat
+wrapper function. E.g.
+
+Linux or Mac:
+ ./scons [Options...]
+
+Windows:
+ scons.bat [Options...]
+"""
+
+#------------------------------------------------------------------------------
+HELP_STRING = """
+===============================================================================
+Help for NaCl SDK Examples
+===============================================================================
+
+* cleaning: ./scons -c
+* build a target: ./scons <target>
+* clean a target: ./scons -c <target>
+
+Supported targets:
+ * fullscreen_tumbler Build the fullscreen-tumbler example.
+ * geturl Build the geturl example.
+ * hello_world Build the hello_world example.
+ * hello_world_c Build the hello_world_c example.
+ * input_events Build the input_events example.
+ * load_progress Build the load_progress example.
+ * mouselock Build the mouselock example.
+ * multithreaded_input_events Build the multithreaded input_events example.
+ * pi_generator Build the pi_generator example.
+ * pong Build the pong example.
+ * sine_synth Build the sine_synth example.
+ * tumbler Build the tumbler example.
+"""
+
+example_directories = [
+ 'fullscreen_tumbler',
+ 'geturl',
+ 'hello_world',
+ 'hello_world_c',
+ 'input_events',
+ 'load_progress',
+ 'mouselock',
+ 'multithreaded_input_events',
+ 'pi_generator',
+ 'pong',
+ 'sine_synth',
+ 'tumbler',
+ ]
+
+Help(HELP_STRING)
+
+staging_dir = os.path.abspath(os.getenv(
+ 'NACL_INSTALL_ROOT', os.path.join(os.getenv('NACL_SDK_ROOT', '.'),
+ 'staging')))
+general_files = Install(staging_dir, ['httpd.py'])
+general_files.extend(InstallAs(os.path.join(staging_dir, 'index.html'),
+ 'index_staging.html'))
+
+if sys.platform in ['win32', 'cygwin']:
+ general_files.extend(Install(staging_dir, 'httpd.cmd'))
+
+SConscript([os.path.join(dir, 'build.scons') for dir in example_directories])
+
+Default(['install'] + general_files + example_directories)
+if GetOption('clean'):
+ print "Removing the staging directory at %s" % staging_dir
+ shutil.rmtree(staging_dir, ignore_errors=True)
diff --git a/native_client_sdk/src/examples/common/check_browser.js b/native_client_sdk/src/examples/common/check_browser.js
new file mode 100644
index 0000000..9636ad0
--- /dev/null
+++ b/native_client_sdk/src/examples/common/check_browser.js
@@ -0,0 +1,178 @@
+/*
+ * Copyright (c) 2011 The Native Client Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**
+ * @fileoverview This file provides a BrowserChecker Javascript class.
+ * Users can create a BrowserChecker object, invoke checkBrowser(|version|),
+ * and then use getIsValidBrowser() and getBrowserSupportStatus()
+ * to determine if the browser version is greater than |version|
+ * and if the Native Client plugin is found.
+ */
+
+// Create a namespace object
+var browser_version = browser_version || {};
+
+/**
+ * Class to provide checking for version and NativeClient.
+ * @param {integer} arg1 An argument that indicates major version of Chrome we
+ * require, such as 14.
+ */
+
+/**
+ * Constructor for the BrowserChecker. Sets the major version of
+ * Chrome that is required to |minChromeVersion|.
+ * @param minChromeVersion The earliest major version of chrome that
+ * is supported. If the Chrome browser version is less than
+ * |minChromeVersion| then |isValidBrowswer| will be set to false.
+ * @param opt_maxChromeVersion Ignored. Retained for backwards compatibility.
+ * @param appVersion The application version string.
+ * @param plugins The plugins that exist in the browser.
+ * @constructor
+ */
+browser_version.BrowserChecker = function(minChromeVersion,
+ appVersion, plugins,
+ opt_maxChromeVersion) {
+ /**
+ * Version specified by the user. This class looks to see if the browser
+ * version is >= |minChromeVersion_|.
+ * @type {integer}
+ * @private
+ */
+ this.minChromeVersion_ = minChromeVersion;
+
+ /**
+ * List of Browser plugin objects.
+ * @type {Ojbect array}
+ * @private
+ */
+ this.plugins_ = plugins;
+
+ /**
+ * Application version string from the Browser.
+ * @type {integer}
+ * @private
+ */
+ this.appVersion_ = appVersion;
+
+ /**
+ * Flag used to indicate if the browser has Native Client and is if the
+ * browser version is recent enough.
+ * @type {boolean}
+ * @private
+ */
+ this.isValidBrowser_ = false;
+
+ /**
+ * Actual major version of Chrome -- found by querying the browser.
+ * @type {integer}
+ * @private
+ */
+ this.chromeVersion_ = null;
+
+ /**
+ * Browser support status. This allows the user to get a detailed status
+ * rather than using this.browserSupportMessage.
+ */
+ this.browserSupportStatus_ =
+ browser_version.BrowserChecker.StatusValues.UNKNOWN;
+}
+
+/**
+ * The values used for BrowserChecker status to indicate success or
+ * a specific error.
+ * @enum {id}
+ */
+browser_version.BrowserChecker.StatusValues = {
+ UNKNOWN: 0,
+ NACL_ENABLED: 1,
+ UNKNOWN_BROWSER: 2,
+ CHROME_VERSION_TOO_OLD: 3,
+ NACL_NOT_ENABLED: 4,
+ NOT_USING_SERVER: 5
+};
+
+/**
+ * Determines if the plugin with name |name| exists in the browser.
+ * @param {string} name The name of the plugin.
+ * @param {Object array} plugins The plugins in this browser.
+ * @return {bool} |true| if the plugin is found.
+ */
+browser_version.BrowserChecker.prototype.pluginExists = function(name,
+ plugins) {
+ for (var index=0; index < plugins.length; index++) {
+ var plugin = this.plugins_[index];
+ var plugin_name = plugin['name'];
+ // If the plugin is not found, you can use the Javascript console
+ // to see the names of the plugins that were found when debugging.
+ if (plugin_name.indexOf(name) != -1) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Returns browserSupportStatus_ which indicates if the browser supports
+ * Native Client. Values are defined as literals in
+ * browser_version.BrowserChecker.StatusValues.
+ * @ return {int} Level of NaCl support.
+ */
+browser_version.BrowserChecker.prototype.getBrowserSupportStatus = function() {
+ return this.browserSupportStatus_;
+}
+
+/**
+ * Returns isValidBrowser (true/false) to indicate if the browser supports
+ * Native Client.
+ * @ return {bool} If this browser has NativeClient and correct version.
+ */
+browser_version.BrowserChecker.prototype.getIsValidBrowser = function() {
+ return this.isValidBrowser_;
+}
+
+/**
+ * Checks to see if this browser can support Native Client applications.
+ * For Chrome browsers, checks to see if the "Native Client" plugin is
+ * enabled.
+ */
+browser_version.BrowserChecker.prototype.checkBrowser = function() {
+ var versionPatt = /Chrome\/(\d+)\.(\d+)\.(\d+)\.(\d+)/;
+ var result = this.appVersion_.match(versionPatt);
+
+ // |result| stores the Chrome version number.
+ if (!result) {
+ this.isValidBrowser_ = false;
+ this.browserSupportStatus_ =
+ browser_version.BrowserChecker.StatusValues.UNKNOWN_BROWSER;
+ } else {
+ this.chromeVersion_ = result[1];
+ // We know we have Chrome, check version and/or plugin named Native Client
+ if (this.chromeVersion_ >= this.minChromeVersion_) {
+ var found_nacl = this.pluginExists('Native Client', this.plugins_);
+ if (found_nacl) {
+ this.isValidBrowser_ = true;
+ this.browserSupportStatus_ =
+ browser_version.BrowserChecker.StatusValues.NACL_ENABLED;
+ } else {
+ this.isValidBrowser_ = false;
+ this.browserSupportStatus_ =
+ browser_version.BrowserChecker.StatusValues.NACL_NOT_ENABLED;
+ }
+ } else {
+ // We are in a version that is less than |minChromeVersion_|
+ this.isValidBrowser_ = false;
+ this.browserSupportStatus_ =
+ browser_version.BrowserChecker.StatusValues.CHROME_VERSION_TOO_OLD;
+ }
+ }
+ var my_protocol = window.location.protocol;
+ if (my_protocol.indexOf('file') == 0) {
+ this.isValidBrowser_ = false;
+ this.browserSupportStatus_ =
+ browser_version.BrowserChecker.StatusValues.NOT_USING_SERVER;
+ }
+}
+
diff --git a/native_client_sdk/src/examples/favicon.ico b/native_client_sdk/src/examples/favicon.ico
new file mode 100644
index 0000000..ee7c943
--- /dev/null
+++ b/native_client_sdk/src/examples/favicon.ico
Binary files differ
diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/bind.js b/native_client_sdk/src/examples/fullscreen_tumbler/bind.js
new file mode 100644
index 0000000..92fbbd2
--- /dev/null
+++ b/native_client_sdk/src/examples/fullscreen_tumbler/bind.js
@@ -0,0 +1,22 @@
+// Copyright (c) 2011 The Native Client Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview This class implements an extension to Function object that
+ * lets you bind a scope for |this| to a function.
+ */
+
+/**
+ * Bind a scope to a function. Used to bind an object to |this| for event
+ * handlers.
+ * @param {!Object} scope The scope in which the function executes. |scope|
+ * becomes |this| during function execution.
+ * @return {function} the bound version of the original function.
+ */
+Function.prototype.bind = function(scope) {
+ var boundContext = this;
+ return function() {
+ return boundContext.apply(scope, arguments);
+ }
+}
diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/build.scons b/native_client_sdk/src/examples/fullscreen_tumbler/build.scons
new file mode 100644
index 0000000..26d14f2
--- /dev/null
+++ b/native_client_sdk/src/examples/fullscreen_tumbler/build.scons
@@ -0,0 +1,66 @@
+#! -*- python -*-
+#
+# Copyright (c) 2011 The Native Client Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import make_nacl_env
+import nacl_utils
+import os
+
+nacl_env = make_nacl_env.NaClEnvironment(
+ use_c_plus_plus_libs=True, nacl_platform=os.getenv('NACL_TARGET_PLATFORM'),
+ install_subdir='fullscreen_tumbler', lib_prefix='..')
+nacl_env.Append(
+ # Add a CPPPATH that enables the full-path #include directives, such as
+ # #include "examples/sine_synth/sine_synth.h"
+ CPPPATH=[os.path.dirname(os.path.dirname(os.getcwd()))],
+ # Strict ANSI compliance.
+ EXTRA_CCFLAGS=['-pedantic'],
+ LIBS=['ppapi_gles2'],
+ )
+
+sources = [
+ 'cube.cc',
+ 'opengl_context.cc',
+ 'scripting_bridge.cc',
+ 'shader_util.cc',
+ 'transforms.cc',
+ 'tumbler.cc',
+ 'tumbler_module.cc',
+ ]
+
+opt_nexes, dbg_nexes = nacl_env.AllNaClModules(sources, 'fullscreen_tumbler')
+
+# This target is used by the SDK build system to provide a prebuilt version
+# of the example in the SDK installer.
+nacl_env.InstallPrebuilt('fullscreen_tumbler')
+
+common_files = [
+ 'check_browser.js',
+ ]
+common_files = [
+ os.path.join(os.path.dirname(os.getcwd()), 'common', common_file)
+ for common_file in common_files]
+
+app_files = [
+ 'fullscreen_tumbler.html',
+ 'fullscreen_tumbler.nmf',
+ 'bind.js',
+ 'dragger.js',
+ 'trackball.js',
+ 'tumbler.js',
+ 'vector3.js',
+ ]
+
+# Split the install of the .nexes from the other app sources so that the strip
+# action is applied to the .nexes only.
+install_nexes = nacl_env.NaClStrippedInstall(dir=nacl_env['NACL_INSTALL_ROOT'],
+ source=opt_nexes)
+install_app = nacl_env.Install(dir=nacl_env['NACL_INSTALL_ROOT'],
+ source=app_files)
+common_dir = os.path.join(os.path.dirname(nacl_env['NACL_INSTALL_ROOT']),
+ 'common')
+install_common = nacl_env.Install(dir=common_dir, source=common_files)
+nacl_env.Alias('install',
+ source=[install_app, install_common, install_nexes])
diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/callback.h b/native_client_sdk/src/examples/fullscreen_tumbler/callback.h
new file mode 100644
index 0000000..4d67262
--- /dev/null
+++ b/native_client_sdk/src/examples/fullscreen_tumbler/callback.h
@@ -0,0 +1,92 @@
+// Copyright (c) 2011 The Native Client Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXAMPLES_TUMBLER_CALLBACK_H_
+#define EXAMPLES_TUMBLER_CALLBACK_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+namespace tumbler {
+
+class ScriptingBridge;
+
+// Templates used to support method call-backs when a method or property is
+// accessed from the browser code.
+
+// Class suite used to publish a method name to Javascript. Typical use is
+// like this:
+// photo::MethodCallback<Calculator>* calculate_callback_;
+// calculate_callback_ =
+// new scripting::MethodCallback<Calculator>(this,
+// &Calculator::Calculate);
+// bridge->AddMethodNamed("calculate", calculate_callback_);
+// ...
+// delete calculate_callback_;
+//
+// The caller must delete the callback.
+
+// Methods get parameters as a dictionary that maps parameter names to values.
+typedef std::map<std::string, std::string> MethodParameter;
+
+// Pure virtual class used in STL containers.
+class MethodCallbackExecutor {
+ public:
+ virtual ~MethodCallbackExecutor() {}
+ virtual void Execute(
+ const ScriptingBridge& bridge,
+ const MethodParameter& parameters) = 0;
+};
+
+template <class T>
+class MethodCallback : public MethodCallbackExecutor {
+ public:
+ typedef void (T::*Method)(
+ const ScriptingBridge& bridge,
+ const MethodParameter& parameters);
+
+ MethodCallback(T* instance, Method method)
+ : instance_(instance), method_(method) {}
+ virtual ~MethodCallback() {}
+ virtual void Execute(
+ const ScriptingBridge& bridge,
+ const MethodParameter& parameters) {
+ // Use "this->" to force C++ to look inside our templatized base class; see
+ // Effective C++, 3rd Ed, item 43, p210 for details.
+ ((this->instance_)->*(this->method_))(bridge, parameters);
+ }
+
+ private:
+ T* instance_;
+ Method method_;
+};
+
+template <class T>
+class ConstMethodCallback : public MethodCallbackExecutor {
+ public:
+ typedef void (T::*ConstMethod)(
+ const ScriptingBridge& bridge,
+ const MethodParameter& parameters) const;
+
+ ConstMethodCallback(const T* instance, ConstMethod method)
+ : instance_(instance), const_method_(method) {}
+ virtual ~ConstMethodCallback() {}
+ virtual void Execute(
+ const ScriptingBridge& bridge,
+ const MethodParameter& parameters) {
+ // Use "this->" to force C++ to look inside our templatized base class; see
+ // Effective C++, 3rd Ed, item 43, p210 for details.
+ ((this->instance_)->*(this->const_method_))(bridge, parameters);
+ }
+
+ private:
+ const T* instance_;
+ ConstMethod const_method_;
+};
+
+} // namespace tumbler
+
+#endif // EXAMPLES_TUMBLER_CALLBACK_H_
+
diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/cube.cc b/native_client_sdk/src/examples/fullscreen_tumbler/cube.cc
new file mode 100644
index 0000000..5b8bc4c
--- /dev/null
+++ b/native_client_sdk/src/examples/fullscreen_tumbler/cube.cc
@@ -0,0 +1,268 @@
+// Copyright (c) 2011 The Native Client 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 "examples/fullscreen_tumbler/cube.h"
+
+#include <algorithm>
+
+#include "examples/fullscreen_tumbler/shader_util.h"
+#include "examples/fullscreen_tumbler/transforms.h"
+
+namespace tumbler {
+
+static const size_t kVertexCount = 24;
+static const int kIndexCount = 36;
+
+Cube::Cube(SharedOpenGLContext opengl_context)
+ : opengl_context_(opengl_context),
+ width_(1),
+ height_(1) {
+ eye_[0] = eye_[1] = 0.0f;
+ eye_[2] = 2.0f;
+ orientation_[0] = 0.0f;
+ orientation_[1] = 0.0f;
+ orientation_[2] = 0.0f;
+ orientation_[3] = 1.0f;
+}
+
+Cube::~Cube() {
+ glDeleteBuffers(3, cube_vbos_);
+ glDeleteProgram(shader_program_object_);
+}
+
+void Cube::PrepareOpenGL() {
+ CreateShaders();
+ CreateCube();
+ glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
+ glEnable(GL_DEPTH_TEST);
+}
+
+void Cube::Resize(int width, int height) {
+ width_ = std::max(width, 1);
+ height_ = std::max(height, 1);
+ // Set the viewport
+ glViewport(0, 0, width_, height_);
+ // Compute the perspective projection matrix with a 60 degree FOV.
+ GLfloat aspect = static_cast<GLfloat>(width_) / static_cast<GLfloat>(height_);
+ transform_4x4::LoadIdentity(perspective_proj_);
+ transform_4x4::Perspective(perspective_proj_, 60.0f, aspect, 1.0f, 20.0f);
+}
+
+void Cube::Draw() {
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ // Compute a new model-view matrix, then use that to make the composite
+ // model-view-projection matrix: MVP = MV . P.
+ GLfloat model_view[16];
+ ComputeModelViewTransform(model_view);
+ transform_4x4::Multiply(mvp_matrix_, model_view, perspective_proj_);
+
+ glBindBuffer(GL_ARRAY_BUFFER, cube_vbos_[0]);
+ glUseProgram(shader_program_object_);
+ glEnableVertexAttribArray(position_location_);
+ glVertexAttribPointer(position_location_,
+ 3,
+ GL_FLOAT,
+ GL_FALSE,
+ 3 * sizeof(GLfloat),
+ NULL);
+ glEnableVertexAttribArray(color_location_);
+ glBindBuffer(GL_ARRAY_BUFFER, cube_vbos_[1]);
+ glVertexAttribPointer(color_location_,
+ 3,
+ GL_FLOAT,
+ GL_FALSE,
+ 3 * sizeof(GLfloat),
+ NULL);
+ glUniformMatrix4fv(mvp_location_, 1, GL_FALSE, mvp_matrix_);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cube_vbos_[2]);
+ glDrawElements(GL_TRIANGLES, kIndexCount, GL_UNSIGNED_SHORT, 0);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+}
+
+bool Cube::CreateShaders() {
+ const char vertex_shader_src[] =
+ "uniform mat4 u_mvpMatrix; \n"
+ "attribute vec4 a_position; \n"
+ "attribute vec3 a_color; \n"
+ "varying lowp vec4 v_color; \n"
+ "void main() \n"
+ "{ \n"
+ " v_color.xyz = a_color; \n"
+ " v_color.w = 1.0; \n"
+ " gl_Position = u_mvpMatrix * a_position; \n"
+ "} \n";
+
+ const char fragment_shader_src[] =
+ "varying lowp vec4 v_color; \n"
+ "void main() \n"
+ "{ \n"
+ " gl_FragColor = v_color; \n"
+ "} \n";
+
+ // Load the shaders and get a linked program object
+ shader_program_object_ =
+ shader_util::CreateProgramFromVertexAndFragmentShaders(
+ vertex_shader_src, fragment_shader_src);
+ if (shader_program_object_ == 0)
+ return false;
+ position_location_ = glGetAttribLocation(shader_program_object_,
+ "a_position");
+ color_location_ = glGetAttribLocation(shader_program_object_, "a_color");
+ mvp_location_ = glGetUniformLocation(shader_program_object_, "u_mvpMatrix");
+ return true;
+}
+
+void Cube::CreateCube() {
+ static const GLfloat cube_vertices[] = {
+ // Vertex coordinates interleaved with color values
+ // Bottom
+ -0.5f, -0.5f, -0.5f,
+ -0.5f, -0.5f, 0.5f,
+ 0.5f, -0.5f, 0.5f,
+ 0.5f, -0.5f, -0.5f,
+ // Top
+ -0.5f, 0.5f, -0.5f,
+ -0.5f, 0.5f, 0.5f,
+ 0.5f, 0.5f, 0.5f,
+ 0.5f, 0.5f, -0.5f,
+ // Back
+ -0.5f, -0.5f, -0.5f,
+ -0.5f, 0.5f, -0.5f,
+ 0.5f, 0.5f, -0.5f,
+ 0.5f, -0.5f, -0.5f,
+ // Front
+ -0.5f, -0.5f, 0.5f,
+ -0.5f, 0.5f, 0.5f,
+ 0.5f, 0.5f, 0.5f,
+ 0.5f, -0.5f, 0.5f,
+ // Left
+ -0.5f, -0.5f, -0.5f,
+ -0.5f, -0.5f, 0.5f,
+ -0.5f, 0.5f, 0.5f,
+ -0.5f, 0.5f, -0.5f,
+ // Right
+ 0.5f, -0.5f, -0.5f,
+ 0.5f, -0.5f, 0.5f,
+ 0.5f, 0.5f, 0.5f,
+ 0.5f, 0.5f, -0.5f
+ };
+
+ static const GLfloat cube_colors[] = {
+ // Vertex coordinates interleaved with color values
+ // Bottom
+ 1.0, 0.0, 0.0,
+ 1.0, 0.0, 0.0,
+ 1.0, 0.0, 0.0,
+ 1.0, 0.0, 0.0,
+ // Top
+ 0.0, 1.0, 0.0,
+ 0.0, 1.0, 0.0,
+ 0.0, 1.0, 0.0,
+ 0.0, 1.0, 0.0,
+ // Back
+ 0.0, 0.0, 1.0,
+ 0.0, 0.0, 1.0,
+ 0.0, 0.0, 1.0,
+ 0.0, 0.0, 1.0,
+ // Front
+ 1.0, 0.0, 1.0,
+ 1.0, 0.0, 1.0,
+ 1.0, 0.0, 1.0,
+ 1.0, 0.0, 1.0,
+ // Left
+ 1.0, 1.0, 0.0,
+ 1.0, 1.0, 0.0,
+ 1.0, 1.0, 0.0,
+ 1.0, 1.0, 0.0,
+ // Right
+ 0.0, 1.0, 1.0,
+ 0.0, 1.0, 1.0,
+ 0.0, 1.0, 1.0,
+ 0.0, 1.0, 1.0
+ };
+
+ static const GLushort cube_indices[] = {
+ // Bottom
+ 0, 2, 1,
+ 0, 3, 2,
+ // Top
+ 4, 5, 6,
+ 4, 6, 7,
+ // Back
+ 8, 9, 10,
+ 8, 10, 11,
+ // Front
+ 12, 15, 14,
+ 12, 14, 13,
+ // Left
+ 16, 17, 18,
+ 16, 18, 19,
+ // Right
+ 20, 23, 22,
+ 20, 22, 21
+ };
+
+ // Generate the VBOs and upload them to the graphics context.
+ glGenBuffers(3, cube_vbos_);
+ glBindBuffer(GL_ARRAY_BUFFER, cube_vbos_[0]);
+ glBufferData(GL_ARRAY_BUFFER,
+ kVertexCount * sizeof(GLfloat) * 3,
+ cube_vertices,
+ GL_STATIC_DRAW);
+ glBindBuffer(GL_ARRAY_BUFFER, cube_vbos_[1]);
+ glBufferData(GL_ARRAY_BUFFER,
+ kVertexCount * sizeof(GLfloat) * 3,
+ cube_colors,
+ GL_STATIC_DRAW);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cube_vbos_[2]);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER,
+ kIndexCount * sizeof(GL_UNSIGNED_SHORT),
+ cube_indices,
+ GL_STATIC_DRAW);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+}
+
+void Cube::ComputeModelViewTransform(GLfloat* model_view) {
+ // This method takes into account the possiblity that |orientation_|
+ // might not be normalized.
+ double sqrx = orientation_[0] * orientation_[0];
+ double sqry = orientation_[1] * orientation_[1];
+ double sqrz = orientation_[2] * orientation_[2];
+ double sqrw = orientation_[3] * orientation_[3];
+ double sqrLength = 1.0 / (sqrx + sqry + sqrz + sqrw);
+
+ transform_4x4::LoadIdentity(model_view);
+ model_view[0] = (sqrx - sqry - sqrz + sqrw) * sqrLength;
+ model_view[5] = (-sqrx + sqry - sqrz + sqrw) * sqrLength;
+ model_view[10] = (-sqrx - sqry + sqrz + sqrw) * sqrLength;
+
+ double temp1 = orientation_[0] * orientation_[1];
+ double temp2 = orientation_[2] * orientation_[3];
+ model_view[1] = 2.0 * (temp1 + temp2) * sqrLength;
+ model_view[4] = 2.0 * (temp1 - temp2) * sqrLength;
+
+ temp1 = orientation_[0] * orientation_[2];
+ temp2 = orientation_[1] * orientation_[3];
+ model_view[2] = 2.0 * (temp1 - temp2) * sqrLength;
+ model_view[8] = 2.0 * (temp1 + temp2) * sqrLength;
+ temp1 = orientation_[1] * orientation_[2];
+ temp2 = orientation_[0] * orientation_[3];
+ model_view[6] = 2.0 * (temp1 + temp2) * sqrLength;
+ model_view[9] = 2.0 * (temp1 - temp2) * sqrLength;
+ model_view[3] = 0.0;
+ model_view[7] = 0.0;
+ model_view[11] = 0.0;
+
+ // Concatenate the translation to the eye point.
+ model_view[12] = -eye_[0];
+ model_view[13] = -eye_[1];
+ model_view[14] = -eye_[2];
+ model_view[15] = 1.0;
+}
+
+} // namespace tumbler
+
diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/cube.h b/native_client_sdk/src/examples/fullscreen_tumbler/cube.h
new file mode 100644
index 0000000..42af1cc
--- /dev/null
+++ b/native_client_sdk/src/examples/fullscreen_tumbler/cube.h
@@ -0,0 +1,98 @@
+// Copyright (c) 2011 The Native Client Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXAMPLES_TUMBLER_CUBE_H_
+#define EXAMPLES_TUMBLER_CUBE_H_
+
+#include <GLES2/gl2.h>
+#include <vector>
+#include "examples/fullscreen_tumbler/opengl_context.h"
+#include "examples/fullscreen_tumbler/opengl_context_ptrs.h"
+
+namespace tumbler {
+
+// The Cube class provides a place to implement 3D rendering. It has a
+// frame that it occupies in a browser window.
+class Cube {
+ public:
+ explicit Cube(SharedOpenGLContext opengl_context);
+ ~Cube();
+
+ // Called once when a new RenderContext is first bound to the view. The
+ // bound context is guaranteed to be current and valid before calling this
+ // method.
+ void PrepareOpenGL();
+
+ // Called whenever the size of the browser view changes. This method is
+ // called at least once when the view is first made visible. Clamps the
+ // sizes to 1.
+ void Resize(int width, int height);
+
+ // Called every time the view need to be drawn. The bound context is
+ // guaranteed to be current and valid before this method is called. The
+ // visible portion of the context is flushed to the browser after this
+ // method returns.
+ void Draw();
+
+ // Accessor for width and height. To change these, call Resize.
+ const int width() const {
+ return width_;
+ }
+
+ const int height() const {
+ return height_;
+ }
+
+ // Accessor/mutator for the camera orientation.
+ void GetOrientation(std::vector<float>* orientation) const {
+ if (!orientation)
+ return;
+ (*orientation)[0] = static_cast<float>(orientation_[0]);
+ (*orientation)[1] = static_cast<float>(orientation_[1]);
+ (*orientation)[2] = static_cast<float>(orientation_[2]);
+ (*orientation)[3] = static_cast<float>(orientation_[3]);
+ }
+ void SetOrientation(const std::vector<float>& orientation) {
+ orientation_[0] = static_cast<GLfloat>(orientation[0]);
+ orientation_[1] = static_cast<GLfloat>(orientation[1]);
+ orientation_[2] = static_cast<GLfloat>(orientation[2]);
+ orientation_[3] = static_cast<GLfloat>(orientation[3]);
+ }
+
+ private:
+ // Create the shaders used to draw the cube, and link them into a program.
+ // Initializes |shader_progam_object_|, |position_loction_| and
+ // |mvp_location_|.
+ bool CreateShaders();
+
+ // Generates a cube as a series of GL_TRIANGLE_STRIPs, and initializes
+ // |index_count_| to the number of indices in the index list used as a VBO.
+ // Creates the |vbo_ids_| required for the vertex and index data and uploads
+ // the the VBO data.
+ void CreateCube();
+
+ // Build up the model-view transform from the eye and orienation properties.
+ // Assumes that |model_view| is a 4x4 matrix.
+ void ComputeModelViewTransform(GLfloat* model_view);
+
+ SharedOpenGLContext opengl_context_;
+ int width_;
+ int height_;
+ GLuint shader_program_object_; // The compiled shaders.
+ GLint position_location_; // The position attribute location.
+ GLint color_location_; // The color attribute location.
+ GLint mvp_location_; // The Model-View-Projection composite matrix.
+ GLuint cube_vbos_[3];
+ GLfloat eye_[3]; // The eye point of the virtual camera.
+ // The orientation of the virtual camera stored as a quaternion. The
+ // quaternion is laid out as {{x, y, z}, w}.
+ GLfloat orientation_[4];
+ GLfloat perspective_proj_[16];
+ GLfloat mvp_matrix_[16];
+};
+
+} // namespace tumbler
+
+#endif // EXAMPLES_TUMBLER_CUBE_H_
+
diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/dragger.js b/native_client_sdk/src/examples/fullscreen_tumbler/dragger.js
new file mode 100644
index 0000000..232d8b5
--- /dev/null
+++ b/native_client_sdk/src/examples/fullscreen_tumbler/dragger.js
@@ -0,0 +1,134 @@
+// Copyright (c) 2011 The Native Client Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview This class implements a mouse-drag event. It registers for
+ * mousedown events, and when it sees one, starts capturing mousemove events
+ * until it gets a mousup event. It manufactures three drag events: the
+ * DRAG_START, DRAG and DRAG_END.
+ */
+
+// Requires bind
+
+/**
+ * Constructor for the Dragger. Register for mousedown events that happen on
+ * |opt_target|. If |opt_target| is null or undefined, then this object
+ * observes mousedown on the whole document.
+ * @param {?Element} opt_target The event target. Defaults to the whole
+ * document.
+ * @constructor
+ */
+tumbler.Dragger = function(opt_target) {
+ /**
+ * The event target.
+ * @type {Element}
+ * @private
+ */
+ this.target_ = opt_target || document;
+
+ /**
+ * The array of objects that get notified of drag events. Each object in
+ * this array get sent a handleStartDrag(), handleDrag() and handleEndDrag()
+ * message.
+ * @type {Array.<Object>}
+ * @private
+ */
+ this.listeners_ = [];
+
+ /**
+ * Flag to indicate whether the object is in a drag sequence or not.
+ * @type {boolean}
+ * @private
+ */
+ this.isDragging_ = false;
+
+ /**
+ * The function objects that get attached as event handlers. These are
+ * cached so that they can be removed on mouse up.
+ * @type {function}
+ * @private
+ */
+ this.boundMouseMove_ = null;
+ this.boundMouseUp_ = null;
+
+ this.target_.addEventListener('mousedown',
+ this.onMouseDown.bind(this),
+ false);
+}
+
+/**
+ * The ids used for drag event types.
+ * @enum {string}
+ */
+tumbler.Dragger.DragEvents = {
+ DRAG_START: 'dragstart', // Start a drag sequence
+ DRAG: 'drag', // Mouse moved during a drag sequence.
+ DRAG_END: 'dragend' // End a drag sewquence.
+};
+
+/**
+ * Add a drag listener. Each listener should respond to thhree methods:
+ * handleStartDrag(), handleDrag() and handleEndDrag(). This method assumes
+ * that |listener| does not already exist in the array of listeners.
+ * @param {!Object} listener The object that will listen to drag events.
+ */
+tumbler.Dragger.prototype.addDragListener = function(listener) {
+ this.listeners_.push(listener);
+}
+
+/**
+ * Handle a mousedown event: register for mousemove and mouseup, then tell
+ * the target that is has a DRAG_START event.
+ * @param {Event} event The mousedown event that triggered this method.
+ */
+tumbler.Dragger.prototype.onMouseDown = function(event) {
+ this.boundMouseMove_ = this.onMouseMove.bind(this);
+ this.boundMouseUp_ = this.onMouseUp.bind(this);
+ this.target_.addEventListener('mousemove', this.boundMouseMove_);
+ this.target_.addEventListener('mouseup', this.boundMouseUp_);
+ this.isDragging_ = true;
+ var dragStartEvent = { type: tumbler.Dragger.DragEvents.DRAG_START,
+ clientX: event.offsetX,
+ clientY: event.offsetY };
+ var i;
+ for (i = 0; i < this.listeners_.length; ++i) {
+ this.listeners_[i].handleStartDrag(this.target_, dragStartEvent);
+ }
+}
+
+/**
+ * Handle a mousemove event: tell the target that is has a DRAG event.
+ * @param {Event} event The mousemove event that triggered this method.
+ */
+tumbler.Dragger.prototype.onMouseMove = function(event) {
+ if (!this.isDragging_)
+ return;
+ var dragEvent = { type: tumbler.Dragger.DragEvents.DRAG,
+ clientX: event.offsetX,
+ clientY: event.offsetY};
+ var i;
+ for (i = 0; i < this.listeners_.length; ++i) {
+ this.listeners_[i].handleDrag(this.target_, dragEvent);
+ }
+}
+
+/**
+ * Handle a mouseup event: un-register for mousemove and mouseup, then tell
+ * the target that is has a DRAG_END event.
+ * @param {Event} event The mouseup event that triggered this method.
+ */
+tumbler.Dragger.prototype.onMouseUp = function(event) {
+ this.target_.removeEventListener('mouseup', this.boundMouseUp_, false);
+ this.target_.removeEventListener('mousemove', this.boundMouseMove_, false);
+ this.boundMouseUp_ = null;
+ this.boundMouseMove_ = null;
+ this.isDragging_ = false;
+ var dragEndEvent = { type: tumbler.Dragger.DragEvents.DRAG_END,
+ clientX: event.offsetX,
+ clientY: event.offsetY};
+ var i;
+ for (i = 0; i < this.listeners_.length; ++i) {
+ this.listeners_[i].handleEndDrag(this.target_, dragEndEvent);
+ }
+}
diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/fullscreen_tumbler.html b/native_client_sdk/src/examples/fullscreen_tumbler/fullscreen_tumbler.html
new file mode 100644
index 0000000..a255b66
--- /dev/null
+++ b/native_client_sdk/src/examples/fullscreen_tumbler/fullscreen_tumbler.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+ <!--
+ Copyright (c) 2011 The Native Client Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style license that can be
+ found in the LICENSE file.
+ -->
+ <head>
+ <title>Interactive Cube Fullscreen Example</title>
+ <script type="text/javascript">
+ // Provide the tumbler namespace
+ tumbler = {};
+ // Fullscreen support is in Chrome version 16.
+ tumbler.CHROME_MINIMUM_VERSION = 16;
+ </script>
+ <script type="text/javascript" src="../common/check_browser.js"></script>
+ <script type="text/javascript" src="bind.js"></script>
+ <script type="text/javascript" src="dragger.js"></script>
+ <script type="text/javascript" src="tumbler.js"></script>
+ <script type="text/javascript" src="vector3.js"></script>
+ <script type="text/javascript" src="trackball.js"></script>
+ <script type="text/javascript">
+ // Check for Native Client support in the browser before the DOM loads.
+ var isValidBrowser = false;
+ var browserSupportStatus = 0;
+ var checker = new browser_version.BrowserChecker(
+ tumbler.CHROME_MINIMUM_VERSION,
+ navigator["appVersion"],
+ navigator["plugins"]);
+ checker.checkBrowser();
+
+ isValidBrowser = checker.getIsValidBrowser();
+ browserSupportStatus = checker.getBrowserSupportStatus();
+ </script>
+ </head>
+ <body id="bodyId">
+ <h1>Interactive Cube Example</h1>
+ <p>
+ The Native Client module executed in this page draws a 3D cube
+ and allows you to rotate it using a virtual trackball method. To toggle
+ the view to/from fullscreen, press the Enter key.
+ </p>
+ <div id="tumbler_view"></div>
+ <script type="text/javascript">
+ if (browserSupportStatus ==
+ browser_version.BrowserChecker.StatusValues.CHROME_VERSION_TOO_OLD) {
+ alert('This example will only work on Chrome version ' +
+ tumbler.CHROME_MINIMUM_VERSION +
+ ' or later.');
+ } else {
+ tumbler.application = new tumbler.Application();
+ tumbler.application.run('tumbler_view');
+ }
+ </script>
+ </body>
+</HTML>
diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/opengl_context.cc b/native_client_sdk/src/examples/fullscreen_tumbler/opengl_context.cc
new file mode 100644
index 0000000..98ee3c3
--- /dev/null
+++ b/native_client_sdk/src/examples/fullscreen_tumbler/opengl_context.cc
@@ -0,0 +1,84 @@
+// Copyright (c) 2011 The Native Client 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 "examples/fullscreen_tumbler/opengl_context.h"
+
+#include <pthread.h>
+#include "ppapi/cpp/completion_callback.h"
+#include "ppapi/gles2/gl2ext_ppapi.h"
+
+namespace {
+// This is called by the brower when the 3D context has been flushed to the
+// browser window.
+void FlushCallback(void* data, int32_t result) {
+ static_cast<tumbler::OpenGLContext*>(data)->set_flush_pending(false);
+}
+} // namespace
+
+namespace tumbler {
+
+OpenGLContext::OpenGLContext(pp::Instance* instance)
+ : pp::Graphics3DClient(instance),
+ flush_pending_(false) {
+ pp::Module* module = pp::Module::Get();
+ assert(module);
+ gles2_interface_ = static_cast<const struct PPB_OpenGLES2*>(
+ module->GetBrowserInterface(PPB_OPENGLES2_INTERFACE));
+ assert(gles2_interface_);
+}
+
+OpenGLContext::~OpenGLContext() {
+ glSetCurrentContextPPAPI(0);
+}
+
+bool OpenGLContext::MakeContextCurrent(pp::Instance* instance) {
+ if (instance == NULL) {
+ glSetCurrentContextPPAPI(0);
+ return false;
+ }
+ // Lazily create the Pepper context.
+ if (context_.is_null()) {
+ int32_t attribs[] = {
+ PP_GRAPHICS3DATTRIB_ALPHA_SIZE, 8,
+ PP_GRAPHICS3DATTRIB_DEPTH_SIZE, 24,
+ PP_GRAPHICS3DATTRIB_STENCIL_SIZE, 8,
+ PP_GRAPHICS3DATTRIB_SAMPLES, 0,
+ PP_GRAPHICS3DATTRIB_SAMPLE_BUFFERS, 0,
+ PP_GRAPHICS3DATTRIB_WIDTH, size_.width(),
+ PP_GRAPHICS3DATTRIB_HEIGHT, size_.height(),
+ PP_GRAPHICS3DATTRIB_NONE
+ };
+ context_ = pp::Graphics3D(instance, pp::Graphics3D(), attribs);
+ if (context_.is_null()) {
+ glSetCurrentContextPPAPI(0);
+ return false;
+ }
+ instance->BindGraphics(context_);
+ }
+ glSetCurrentContextPPAPI(context_.pp_resource());
+ return true;
+}
+
+void OpenGLContext::InvalidateContext(pp::Instance* instance) {
+ glSetCurrentContextPPAPI(0);
+}
+
+void OpenGLContext::ResizeContext(const pp::Size& size) {
+ size_ = size;
+ if (!context_.is_null()) {
+ context_.ResizeBuffers(size.width(), size.height());
+ }
+}
+
+
+void OpenGLContext::FlushContext() {
+ if (flush_pending()) {
+ // A flush is pending so do nothing; just drop this flush on the floor.
+ return;
+ }
+ set_flush_pending(true);
+ context_.SwapBuffers(pp::CompletionCallback(&FlushCallback, this));
+}
+} // namespace tumbler
+
diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/opengl_context.h b/native_client_sdk/src/examples/fullscreen_tumbler/opengl_context.h
new file mode 100644
index 0000000..6a5369b
--- /dev/null
+++ b/native_client_sdk/src/examples/fullscreen_tumbler/opengl_context.h
@@ -0,0 +1,94 @@
+// Copyright (c) 2011 The Native Client Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXAMPLES_TUMBLER_OPENGL_CONTEXT_H_
+#define EXAMPLES_TUMBLER_OPENGL_CONTEXT_H_
+
+///
+/// @file
+/// OpenGLContext manages the OpenGL context in the browser that is associated
+/// with a @a pp::Instance instance.
+///
+
+#include <assert.h>
+#include <pthread.h>
+
+#include <algorithm>
+#include <string>
+
+#include "examples/fullscreen_tumbler/opengl_context_ptrs.h"
+#include "ppapi/c/ppb_opengles2.h"
+#include "ppapi/cpp/graphics_3d.h"
+#include "ppapi/cpp/graphics_3d_client.h"
+#include "ppapi/cpp/instance.h"
+#include "ppapi/cpp/size.h"
+
+namespace tumbler {
+
+/// OpenGLContext manages an OpenGL rendering context in the browser.
+///
+class OpenGLContext : public pp::Graphics3DClient {
+ public:
+ explicit OpenGLContext(pp::Instance* instance);
+
+ /// Release all the in-browser resources used by this context, and make this
+ /// context invalid.
+ virtual ~OpenGLContext();
+
+ /// The Graphics3DClient interfcace.
+ virtual void Graphics3DContextLost() {
+ assert(!"Unexpectedly lost graphics context");
+ }
+
+ /// Make @a this the current 3D context in @a instance.
+ /// @param instance The instance of the NaCl module that will receive the
+ /// the current 3D context.
+ /// @return success.
+ bool MakeContextCurrent(pp::Instance* instance);
+
+ /// Flush the contents of this context to the browser's 3D device.
+ void FlushContext();
+
+ /// Make the underlying 3D device invalid, so that any subsequent rendering
+ /// commands will have no effect. The next call to MakeContextCurrent() will
+ /// cause the underlying 3D device to get rebound and start receiving
+ /// receiving rendering commands again. Use InvalidateContext(), for
+ /// example, when resizing the context's viewing area.
+ void InvalidateContext(pp::Instance* instance);
+
+ /// Resize the context.
+ void ResizeContext(const pp::Size& size);
+
+ /// The OpenGL ES 2.0 interface.
+ const struct PPB_OpenGLES2* gles2() const {
+ return gles2_interface_;
+ }
+
+ /// The PP_Resource needed to make GLES2 calls through the Pepper interface.
+ const PP_Resource gl_context() const {
+ return context_.pp_resource();
+ }
+
+ /// Indicate whether a flush is pending. This can only be called from the
+ /// main thread; it is not thread safe.
+ bool flush_pending() const {
+ return flush_pending_;
+ }
+ void set_flush_pending(bool flag) {
+ flush_pending_ = flag;
+ }
+
+ private:
+ pp::Size size_;
+ pp::Graphics3D context_;
+ bool flush_pending_;
+
+ const struct PPB_OpenGLES2* gles2_interface_;
+};
+
+} // namespace tumbler
+
+#endif // EXAMPLES_TUMBLER_OPENGL_CONTEXT_H_
+
+
diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/opengl_context_ptrs.h b/native_client_sdk/src/examples/fullscreen_tumbler/opengl_context_ptrs.h
new file mode 100644
index 0000000..3478521
--- /dev/null
+++ b/native_client_sdk/src/examples/fullscreen_tumbler/opengl_context_ptrs.h
@@ -0,0 +1,22 @@
+// Copyright (c) 2011 The Native Client Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXAMPLES_TUMBLER_OPENGL_CONTEXT_PTRS_H_
+#define EXAMPLES_TUMBLER_OPENGL_CONTEXT_PTRS_H_
+
+// A convenience wrapper for a shared OpenGLContext pointer type. As other
+// smart pointer types are needed, add them here.
+
+#include <tr1/memory>
+
+namespace tumbler {
+
+class OpenGLContext;
+
+typedef std::tr1::shared_ptr<OpenGLContext> SharedOpenGLContext;
+
+} // namespace tumbler
+
+#endif // EXAMPLES_TUMBLER_OPENGL_CONTEXT_PTRS_H_
+
diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/scripting_bridge.cc b/native_client_sdk/src/examples/fullscreen_tumbler/scripting_bridge.cc
new file mode 100644
index 0000000..8a7a54d
--- /dev/null
+++ b/native_client_sdk/src/examples/fullscreen_tumbler/scripting_bridge.cc
@@ -0,0 +1,96 @@
+// Copyright (c) 2011 The Native Client 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 "examples/fullscreen_tumbler/scripting_bridge.h"
+
+namespace {
+const char* const kWhiteSpaceCharacters = " \t";
+
+// Helper function to pull out the next token in |token_string|. A token is
+// delimited by whitespace. Scanning begins at |*pos|, if pos goes beyond the
+// end of |token_string|, it is set to std::string::npos and an empty string
+// is returned. On return, |*pos| will point to the beginning of the next
+// token. |pos| must not be NULL.
+const std::string ScanToken(const std::string& token_string, size_t* pos) {
+ std::string token;
+ if (*pos == std::string::npos) {
+ return token;
+ }
+ size_t token_start_pos = token_string.find_first_not_of(kWhiteSpaceCharacters,
+ *pos);
+ size_t token_end_pos = token_string.find_first_of(kWhiteSpaceCharacters,
+ token_start_pos);
+ if (token_start_pos != std::string::npos) {
+ token = token_string.substr(token_start_pos, token_end_pos);
+ }
+ *pos = token_end_pos;
+ return token;
+}
+
+// Take a string of the form 'name:value' and split it into two strings, one
+// containing 'name' and the other 'value'. If the ':' separator is missing,
+// or is the last character in |parameter|, |parameter| is copied to
+// |param_name|, |param_value| is left unchanged and false is returned.
+bool ParseParameter(const std::string& parameter,
+ std::string* param_name,
+ std::string* param_value) {
+ bool success = false;
+ size_t sep_pos = parameter.find_first_of(':');
+ if (sep_pos != std::string::npos) {
+ *param_name = parameter.substr(0, sep_pos);
+ if (sep_pos < parameter.length() - 1) {
+ *param_value = parameter.substr(sep_pos + 1);
+ success = true;
+ } else {
+ success = false;
+ }
+ } else {
+ *param_name = parameter;
+ success = false;
+ }
+ return success;
+}
+} // namespace
+
+namespace tumbler {
+
+bool ScriptingBridge::AddMethodNamed(const std::string& method_name,
+ SharedMethodCallbackExecutor method) {
+ if (method_name.size() == 0 || method == NULL)
+ return false;
+ method_dictionary_.insert(
+ std::pair<std::string, SharedMethodCallbackExecutor>(method_name,
+ method));
+ return true;
+}
+
+bool ScriptingBridge::InvokeMethod(const std::string& method) {
+ size_t current_pos = 0;
+ const std::string method_name = ScanToken(method, &current_pos);
+ MethodDictionary::iterator method_iter;
+ method_iter = method_dictionary_.find(method_name);
+ if (method_iter != method_dictionary_.end()) {
+ // Pull out the method parameters and build a dictionary that maps
+ // parameter names to values.
+ std::map<std::string, std::string> param_dict;
+ while (current_pos != std::string::npos) {
+ const std::string parameter = ScanToken(method, &current_pos);
+ if (parameter.length()) {
+ std::string param_name;
+ std::string param_value;
+ if (ParseParameter(parameter, &param_name, &param_value)) {
+ // Note that duplicate parameter names will override each other. The
+ // last one in the method string will be used.
+ param_dict[param_name] = param_value;
+ }
+ }
+ }
+ (*method_iter->second).Execute(*this, param_dict);
+ return true;
+ }
+ return false;
+}
+
+} // namespace tumbler
+
diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/scripting_bridge.h b/native_client_sdk/src/examples/fullscreen_tumbler/scripting_bridge.h
new file mode 100644
index 0000000..b1888cf
--- /dev/null
+++ b/native_client_sdk/src/examples/fullscreen_tumbler/scripting_bridge.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2011 The Native Client Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXAMPLES_TUMBLER_SCRIPTING_BRIDGE_H_
+#define EXAMPLES_TUMBLER_SCRIPTING_BRIDGE_H_
+
+#include <map>
+#include <string>
+#include <tr1/memory>
+#include <vector>
+
+#include "examples/fullscreen_tumbler/callback.h"
+#include "ppapi/cpp/var.h"
+
+namespace tumbler {
+
+class MethodCallbackExecutor;
+
+// This class handles the interface between the browser and the NaCl module.
+// There is a single point of entry from the browser: postMessage(). The
+// string passed to postMessage() has this format:
+// 'function_name arg_name0:arg_0 arg_name1:arg1 ...'
+// The arguments have undetermined type; they are placed in a map of argument
+// names and values. Values are all strings, it is up to the target code to
+// do any type coercion.
+// Methods called by the scripting bridge must have a signature like this:
+// void Method(const ScriptingBridge& bridge,
+// const ParameterDictionary&);
+class ScriptingBridge {
+ public:
+ // Shared pointer type used in the method map.
+ typedef std::tr1::shared_ptr<MethodCallbackExecutor>
+ SharedMethodCallbackExecutor;
+
+ virtual ~ScriptingBridge() {}
+
+ // Causes |method_name| to be published as a method that can be called via
+ // postMessage() from the browser. Associates this method with |method|.
+ bool AddMethodNamed(const std::string& method_name,
+ SharedMethodCallbackExecutor method);
+
+ bool InvokeMethod(const std::string& method);
+
+ private:
+ typedef std::map<std::string, SharedMethodCallbackExecutor> MethodDictionary;
+
+ MethodDictionary method_dictionary_;
+};
+
+} // namespace tumbler
+#endif // EXAMPLES_TUMBLER_SCRIPTING_BRIDGE_H_
+
diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/shader_util.cc b/native_client_sdk/src/examples/fullscreen_tumbler/shader_util.cc
new file mode 100644
index 0000000..c2c647d
--- /dev/null
+++ b/native_client_sdk/src/examples/fullscreen_tumbler/shader_util.cc
@@ -0,0 +1,97 @@
+// Copyright (c) 2011 The Native Client 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 "examples/fullscreen_tumbler/shader_util.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+namespace shader_util {
+
+GLuint CreateShaderOfType(GLenum type, const char *shader_src) {
+ GLuint shader;
+ GLint compiled;
+
+ // Create the shader object
+ shader = glCreateShader(type);
+
+ if (shader == 0)
+ return 0;
+
+ // Load and compile the shader source
+ glShaderSource(shader, 1, &shader_src, NULL);
+ glCompileShader(shader);
+
+ // Check the compile status
+ glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
+ if (compiled == 0) {
+ GLint info_len = 0;
+ glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &info_len);
+ if (info_len > 1) {
+ char* info_log =
+ reinterpret_cast<char*>(malloc(sizeof(*info_log) * info_len));
+ glGetShaderInfoLog(shader, info_len, NULL, info_log);
+ // TODO(dspringer): We could really use a logging API.
+ printf("Error compiling shader:\n%s\n", info_log);
+ free(info_log);
+ }
+ glDeleteShader(shader);
+ return 0;
+ }
+
+ return shader;
+}
+
+GLuint CreateProgramFromVertexAndFragmentShaders(
+ const char *vertex_shader_src, const char *fragment_shader_src) {
+ GLuint vertex_shader;
+ GLuint fragment_shader;
+ GLuint program_object;
+ GLint linked;
+
+ // Load the vertex/fragment shaders
+ vertex_shader = CreateShaderOfType(GL_VERTEX_SHADER, vertex_shader_src);
+ if (vertex_shader == 0)
+ return 0;
+ fragment_shader = CreateShaderOfType(GL_FRAGMENT_SHADER, fragment_shader_src);
+ if (fragment_shader == 0) {
+ glDeleteShader(vertex_shader);
+ return 0;
+ }
+
+ // Create the program object and attach the shaders.
+ program_object = glCreateProgram();
+ if (program_object == 0)
+ return 0;
+ glAttachShader(program_object, vertex_shader);
+ glAttachShader(program_object, fragment_shader);
+
+ // Link the program
+ glLinkProgram(program_object);
+
+ // Check the link status
+ glGetProgramiv(program_object, GL_LINK_STATUS, &linked);
+ if (linked == 0) {
+ GLint info_len = 0;
+ glGetProgramiv(program_object, GL_INFO_LOG_LENGTH, &info_len);
+ if (info_len > 1) {
+ char* info_log = reinterpret_cast<char*>(malloc(info_len));
+ glGetProgramInfoLog(program_object, info_len, NULL, info_log);
+ // TODO(dspringer): We could really use a logging API.
+ printf("Error linking program:\n%s\n", info_log);
+ free(info_log);
+ }
+ glDeleteProgram(program_object);
+ return 0;
+ }
+
+ // Delete these here because they are attached to the program object.
+ glDeleteShader(vertex_shader);
+ glDeleteShader(fragment_shader);
+
+ return program_object;
+}
+
+} // namespace shader_util
+
diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/shader_util.h b/native_client_sdk/src/examples/fullscreen_tumbler/shader_util.h
new file mode 100644
index 0000000..fdf9cbd
--- /dev/null
+++ b/native_client_sdk/src/examples/fullscreen_tumbler/shader_util.h
@@ -0,0 +1,30 @@
+// Copyright (c) 2011 The Native Client Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Some simple helper functions that load shaders and create program objects.
+
+#ifndef EXAMPLES_TUMBLER_SHADER_UTIL_H_
+#define EXAMPLES_TUMBLER_SHADER_UTIL_H_
+
+#include <GLES2/gl2.h>
+
+namespace shader_util {
+
+// Load and compile a shader. |type| can be one of GL_VERTEX_SHADER or
+// GL_FRAGMENT_SHADER. Returns a non-0 value representing the compiled
+// shader on success, 0 on failure. The caller is responsible for deleting
+// the returned shader using glDeleteShader().
+GLuint CreateShaderOfType(GLenum type, const char *shader_src);
+
+// Load and compile the vertex and fragment shaders, then link these together
+// into a complete program. Returns a non-0 value representing the program on,
+// success or 0 on failure. The caller is responsible for deleting the
+// returned program using glDeleteProgram().
+GLuint CreateProgramFromVertexAndFragmentShaders(
+ const char *vertex_shader_src, const char *fragment_shader_src);
+
+} // namespace shader_util
+
+#endif // EXAMPLES_TUMBLER_SHADER_UTIL_H_
+
diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/trackball.js b/native_client_sdk/src/examples/fullscreen_tumbler/trackball.js
new file mode 100644
index 0000000..88b9a62
--- /dev/null
+++ b/native_client_sdk/src/examples/fullscreen_tumbler/trackball.js
@@ -0,0 +1,296 @@
+// Copyright (c) 2011 The Native Client Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview Implement a virtual trackball in the tumbler.Trackball
+ * class. This class maps 2D mouse events to 3D rotations by simulating a
+ * trackball that you roll by dragging the mouse. There are two principle
+ * methods in the class: startAtPointInFrame which you use to begin a trackball
+ * simulation and rollToPoint, which you use while dragging the mouse. The
+ * rollToPoint method returns a rotation expressed as a quaternion.
+ */
+
+
+// Requires tumbler.Application
+// Requires tumbler.DragEvent
+// Requires tumbler.Vector3
+
+/**
+ * Constructor for the Trackball object. This class maps 2D mouse drag events
+ * into 3D rotations by simulating a trackball. The idea is to simulate
+ * clicking on the trackball, and then rolling it as you drag the mouse.
+ * The math behind the trackball is simple: start with a vector from the first
+ * mouse-click on the ball to the center of the 3D view. At the same time, set
+ * the radius of the ball to be the smaller dimension of the 3D view. As you
+ * drag the mouse around in the 3D view, a second vector is computed from the
+ * surface of the ball to the center. The axis of rotation is the cross
+ * product of these two vectors, and the angle of rotation is the angle between
+ * the two vectors.
+ * @constructor
+ */
+tumbler.Trackball = function() {
+ /**
+ * The square of the trackball's radius. The math never looks at the radius,
+ * but looks at the radius squared.
+ * @type {number}
+ * @private
+ */
+ this.sqrRadius_ = 0;
+
+ /**
+ * The 3D vector representing the point on the trackball where the mouse
+ * was clicked. Default is pointing stright through the center of the ball.
+ * @type {Object}
+ * @private
+ */
+ this.rollStart_ = new tumbler.Vector3(0, 0, 1);
+
+ /**
+ * The 2D center of the frame that encloses the trackball.
+ * @type {!Object}
+ * @private
+ */
+ this.center_ = { x: 0, y: 0 };
+
+ /**
+ * Cached camera orientation. When a drag START event happens this is set to
+ * the current orientation in the calling view's plugin. The default is the
+ * identity quaternion.
+ * @type {Array.<number>}
+ * @private
+ */
+ this.cameraOrientation_ = [0, 0, 0, 1];
+};
+
+/**
+ * Compute the dimensions of the virtual trackball to fit inside |frameSize|.
+ * The radius of the trackball is set to be 1/2 of the smaller of the two frame
+ * dimensions, the center point is at the midpoint of each side.
+ * @param {!goog.math.Size} frameSize 2D-point representing the size of the
+ * element that encloses the virtual trackball.
+ * @private
+ */
+tumbler.Trackball.prototype.initInFrame_ = function(frameSize) {
+ // Compute the radius of the virtual trackball. This is 1/2 of the smaller
+ // of the frame's width and height.
+ var halfFrameSize = 0.5 * Math.min(frameSize.width, frameSize.height);
+ // Cache the square of the trackball's radius.
+ this.sqrRadius_ = halfFrameSize * halfFrameSize;
+ // Figure the center of the view.
+ this.center_.x = frameSize.width * 0.5;
+ this.center_.y = frameSize.height * 0.5;
+};
+
+/**
+ * Method to convert (by translation) a 2D client point from a coordinate space
+ * with origin in the lower-left corner of the client view to a space with
+ * origin in the center of the client view. Use this method before mapping the
+ * 2D point to he 3D tackball point (see also the projectOnTrackball_() method).
+ * Call the startAtPointInFrame before calling this method so that the
+ * |center_| property is correctly initialized.
+ * @param {!Object} clientPoint map this point to the coordinate space with
+ * origin in thecenter of the client view.
+ * @return {Object} the converted point.
+ * @private
+ */
+tumbler.Trackball.prototype.convertClientPoint_ = function(clientPoint) {
+ var difference = { x: clientPoint.x - this.center_.x,
+ y: clientPoint.y - this.center_.y }
+ return difference;
+};
+
+/**
+ * Method to map a 2D point to a 3D point on the virtual trackball that was set
+ * up using the startAtPointInFrame method. If the point lies outside of the
+ * radius of the virtual trackball, then the z-coordinate of the 3D point
+ * is set to 0.
+ * @param {!Object.<x, y>} point 2D-point in the coordinate space with origin
+ * in the center of the client view.
+ * @return {tumbler.Vector3} the 3D point on the virtual trackball.
+ * @private
+ */
+tumbler.Trackball.prototype.projectOnTrackball_ = function(point) {
+ var sqrRadius2D = point.x * point.x + point.y * point.y;
+ var zValue;
+ if (sqrRadius2D > this.sqrRadius_) {
+ // |point| lies outside the virtual trackball's sphere, so use a virtual
+ // z-value of 0. This is equivalent to clicking on the horizontal equator
+ // of the trackball.
+ zValue = 0;
+ } else {
+ // A sphere can be defined as: r^2 = x^2 + y^2 + z^2, so z =
+ // sqrt(r^2 - (x^2 + y^2)).
+ zValue = Math.sqrt(this.sqrRadius_ - sqrRadius2D);
+ }
+ var trackballPoint = new tumbler.Vector3(point.x, point.y, zValue);
+ return trackballPoint;
+};
+
+/**
+ * Method to start up the trackball. The trackball works by pretending that a
+ * ball encloses the 3D view. You roll this pretend ball with the mouse. For
+ * example, if you click on the center of the ball and move the mouse straight
+ * to the right, you roll the ball around its Y-axis. This produces a Y-axis
+ * rotation. You can click on the "edge" of the ball and roll it around
+ * in a circle to get a Z-axis rotation.
+ * @param {!Object.<x, y>} startPoint 2D-point, usually the mouse-down
+ * point.
+ * @param {!Object.<width, height>} frameSize 2D-point representing the size of
+ * the element that encloses the virtual trackball.
+ */
+tumbler.Trackball.prototype.startAtPointInFrame =
+ function(startPoint, frameSize) {
+ this.initInFrame_(frameSize);
+ // Compute the starting vector from the surface of the ball to its center.
+ this.rollStart_ = this.projectOnTrackball_(
+ this.convertClientPoint_(startPoint));
+};
+
+/**
+ * Method to roll the virtual trackball; call this in response to a mouseDrag
+ * event. Takes |dragPoint| and projects it from 2D mouse coordinates onto the
+ * virtual track ball that was set up in startAtPointInFrame method.
+ * Returns a quaternion that represents the rotation from |rollStart_| to
+ * |rollEnd_|.
+ * @param {!Object.<x, y>} dragPoint 2D-point representing the
+ * destination mouse point.
+ * @return {Array.<number>} a quaternion that represents the rotation from
+ * the point wnere the mouse was clicked on the trackball to this point.
+ * The quaternion looks like this: [[v], cos(angle/2)], where [v] is the
+ * imaginary part of the quaternion and is computed as [x, y, z] *
+ * sin(angle/2).
+ */
+tumbler.Trackball.prototype.rollToPoint = function(dragPoint) {
+ var rollTo = this.convertClientPoint_(dragPoint);
+ if ((Math.abs(this.rollStart_.x - rollTo.x) <
+ tumbler.Trackball.DOUBLE_EPSILON) &&
+ (Math.abs(this.rollStart_.y, rollTo.y) <
+ tumbler.Trackball.DOUBLE_EPSILON)) {
+ // Not enough change in the vectors to roll the ball, return the identity
+ // quaternion.
+ return [0, 0, 0, 1];
+ }
+
+ // Compute the ending vector from the surface of the ball to its center.
+ var rollEnd = this.projectOnTrackball_(rollTo);
+
+ // Take the cross product of the two vectors. r = s X e
+ var rollVector = this.rollStart_.cross(rollEnd);
+ var invStartMag = 1.0 / this.rollStart_.magnitude();
+ var invEndMag = 1.0 / rollEnd.magnitude();
+
+ // cos(a) = (s . e) / (||s|| ||e||)
+ var cosAng = this.rollStart_.dot(rollEnd) * invStartMag * invEndMag;
+ // sin(a) = ||(s X e)|| / (||s|| ||e||)
+ var sinAng = rollVector.magnitude() * invStartMag * invEndMag;
+ // Build a quaternion that represents the rotation about |rollVector|.
+ // Use atan2 for a better angle. If you use only cos or sin, you only get
+ // half the possible angles, and you can end up with rotations that flip
+ // around near the poles.
+ var rollHalfAngle = Math.atan2(sinAng, cosAng) * 0.5;
+ rollVector.normalize();
+ // The quaternion looks like this: [[v], cos(angle/2)], where [v] is the
+ // imaginary part of the quaternion and is computed as [x, y, z] *
+ // sin(angle/2).
+ rollVector.scale(Math.sin(rollHalfAngle));
+ var ballQuaternion = [rollVector.x,
+ rollVector.y,
+ rollVector.z,
+ Math.cos(rollHalfAngle)];
+ return ballQuaternion;
+};
+
+/**
+ * Handle the drag START event: grab the current camera orientation from the
+ * sending view and set up the virtual trackball.
+ * @param {!tumbler.Application} view The view controller that called this
+ * method.
+ * @param {!tumbler.DragEvent} dragStartEvent The DRAG_START event that
+ * triggered this handler.
+ */
+tumbler.Trackball.prototype.handleStartDrag =
+ function(controller, dragStartEvent) {
+ // Cache the camera orientation. The orientations from the trackball as it
+ // rolls are concatenated to this orientation and pushed back into the
+ // plugin on the other side of the JavaScript bridge.
+ controller.setCameraOrientation(this.cameraOrientation_);
+ // Invert the y-coordinate for the trackball computations.
+ var frameSize = { width: controller.offsetWidth,
+ height: controller.offsetHeight };
+ var flippedY = { x: dragStartEvent.clientX,
+ y: frameSize.height - dragStartEvent.clientY };
+ this.startAtPointInFrame(flippedY, frameSize);
+};
+
+/**
+ * Handle the drag DRAG event: concatenate the current orientation to the
+ * cached orientation. Send this final value through to the GSPlugin via the
+ * setValueForKey() method.
+ * @param {!tumbler.Application} view The view controller that called this
+ * method.
+ * @param {!tumbler.DragEvent} dragEvent The DRAG event that triggered this
+ * handler.
+ */
+tumbler.Trackball.prototype.handleDrag =
+ function(controller, dragEvent) {
+ // Flip the y-coordinate so that the 2D origin is in the lower-left corner.
+ var frameSize = { width: controller.offsetWidth,
+ height: controller.offsetHeight };
+ var flippedY = { x: dragEvent.clientX,
+ y: frameSize.height - dragEvent.clientY };
+ controller.setCameraOrientation(
+ tumbler.multQuaternions(this.rollToPoint(flippedY),
+ this.cameraOrientation_));
+};
+
+/**
+ * Handle the drag END event: get the final orientation and concatenate it to
+ * the cached orientation.
+ * @param {!tumbler.Application} view The view controller that called this
+ * method.
+ * @param {!tumbler.DragEvent} dragEndEvent The DRAG_END event that triggered
+ * this handler.
+ */
+tumbler.Trackball.prototype.handleEndDrag =
+ function(controller, dragEndEvent) {
+ // Flip the y-coordinate so that the 2D origin is in the lower-left corner.
+ var frameSize = { width: controller.offsetWidth,
+ height: controller.offsetHeight };
+ var flippedY = { x: dragEndEvent.clientX,
+ y: frameSize.height - dragEndEvent.clientY };
+ this.cameraOrientation_ = tumbler.multQuaternions(this.rollToPoint(flippedY),
+ this.cameraOrientation_);
+ controller.setCameraOrientation(this.cameraOrientation_);
+};
+
+/**
+ * A utility function to multiply two quaterions. Returns the product q0 * q1.
+ * This is effectively the same thing as concatenating the two rotations
+ * represented in each quaternion together. Note that quaternion multiplication
+ * is NOT commutative: q0 * q1 != q1 * q0.
+ * @param {!Array.<number>} q0 A 4-element array representing the first
+ * quaternion.
+ * @param {!Array.<number>} q1 A 4-element array representing the second
+ * quaternion.
+ * @return {Array.<number>} A 4-element array representing the product q0 * q1.
+ */
+tumbler.multQuaternions = function(q0, q1) {
+ // Return q0 * q1 (note the order).
+ var qMult = [
+ q0[3] * q1[0] + q0[0] * q1[3] + q0[1] * q1[2] - q0[2] * q1[1],
+ q0[3] * q1[1] - q0[0] * q1[2] + q0[1] * q1[3] + q0[2] * q1[0],
+ q0[3] * q1[2] + q0[0] * q1[1] - q0[1] * q1[0] + q0[2] * q1[3],
+ q0[3] * q1[3] - q0[0] * q1[0] - q0[1] * q1[1] - q0[2] * q1[2]
+ ];
+ return qMult;
+};
+
+/**
+ * Real numbers that are less than this distance apart are considered
+ * equivalent.
+ * TODO(dspringer): It seems as though there should be a const like this
+ * in Closure somewhere (goog.math?).
+ * @type {number}
+ */
+tumbler.Trackball.DOUBLE_EPSILON = 1.0e-16;
diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/transforms.cc b/native_client_sdk/src/examples/fullscreen_tumbler/transforms.cc
new file mode 100644
index 0000000..609f8fd
--- /dev/null
+++ b/native_client_sdk/src/examples/fullscreen_tumbler/transforms.cc
@@ -0,0 +1,117 @@
+// Copyright (c) 2011 The Native Client 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 "examples/fullscreen_tumbler/transforms.h"
+
+#include <GLES2/gl2.h>
+#include <math.h>
+#include <string.h>
+
+namespace transform_4x4 {
+
+static const GLfloat kPI = 3.1415926535897932384626433832795f;
+
+void Translate(GLfloat* m, GLfloat tx, GLfloat ty, GLfloat tz) {
+ m[12] += (m[0] * tx + m[4] * ty + m[8] * tz);
+ m[13] += (m[1] * tx + m[5] * ty + m[9] * tz);
+ m[14] += (m[2] * tx + m[6] * ty + m[10] * tz);
+ m[15] += (m[3] * tx + m[7] * ty + m[11] * tz);
+}
+
+void Frustum(GLfloat* m,
+ GLfloat left,
+ GLfloat right,
+ GLfloat bottom,
+ GLfloat top,
+ GLfloat near_z,
+ GLfloat far_z) {
+ GLfloat delta_x = right - left;
+ GLfloat delta_y = top - bottom;
+ GLfloat delta_z = far_z - near_z;
+ GLfloat frustum[16];
+
+ if ((near_z <= 0.0f) || (far_z <= 0.0f) ||
+ (delta_x <= 0.0f) || (delta_y <= 0.0f) || (delta_z <= 0.0f))
+ return;
+
+ frustum[0] = 2.0f * near_z / delta_x;
+ frustum[1] = frustum[2] = frustum[3] = 0.0f;
+
+ frustum[5] = 2.0f * near_z / delta_y;
+ frustum[4] = frustum[6] = frustum[7] = 0.0f;
+
+ frustum[8] = (right + left) / delta_x;
+ frustum[9] = (top + bottom) / delta_y;
+ frustum[10] = -(near_z + far_z) / delta_z;
+ frustum[11] = -1.0f;
+
+ frustum[14] = -2.0f * near_z * far_z / delta_z;
+ frustum[12] = frustum[13] = frustum[15] = 0.0f;
+
+ transform_4x4::Multiply(m, frustum, m);
+}
+
+
+void Perspective(GLfloat* m,
+ GLfloat fovy,
+ GLfloat aspect,
+ GLfloat near_z,
+ GLfloat far_z) {
+ GLfloat frustum_w, frustum_h;
+
+ frustum_h = tanf((fovy * 0.5f) / 180.0f * kPI) * near_z;
+ frustum_w = frustum_h * aspect;
+ transform_4x4::Frustum(m, -frustum_w, frustum_w, -frustum_h, frustum_h,
+ near_z, far_z);
+}
+
+void Multiply(GLfloat *m, GLfloat *a, GLfloat* b) {
+ GLfloat tmp[16];
+ // tmp = a . b
+ GLfloat a0, a1, a2, a3;
+ a0 = a[0];
+ a1 = a[1];
+ a2 = a[2];
+ a3 = a[3];
+ tmp[0] = a0 * b[0] + a1 * b[4] + a2 * b[8] + a3 * b[12];
+ tmp[1] = a0 * b[1] + a1 * b[5] + a2 * b[9] + a3 * b[13];
+ tmp[2] = a0 * b[2] + a1 * b[6] + a2 * b[10] + a3 * b[14];
+ tmp[3] = a0 * b[3] + a1 * b[7] + a2 * b[11] + a3 * b[15];
+
+ a0 = a[4];
+ a1 = a[5];
+ a2 = a[6];
+ a3 = a[7];
+ tmp[4] = a0 * b[0] + a1 * b[4] + a2 * b[8] + a3 * b[12];
+ tmp[5] = a0 * b[1] + a1 * b[5] + a2 * b[9] + a3 * b[13];
+ tmp[6] = a0 * b[2] + a1 * b[6] + a2 * b[10] + a3 * b[14];
+ tmp[7] = a0 * b[3] + a1 * b[7] + a2 * b[11] + a3 * b[15];
+
+ a0 = a[8];
+ a1 = a[9];
+ a2 = a[10];
+ a3 = a[11];
+ tmp[8] = a0 * b[0] + a1 * b[4] + a2 * b[8] + a3 * b[12];
+ tmp[9] = a0 * b[1] + a1 * b[5] + a2 * b[9] + a3 * b[13];
+ tmp[10] = a0 * b[2] + a1 * b[6] + a2 * b[10] + a3 * b[14];
+ tmp[11] = a0 * b[3] + a1 * b[7] + a2 * b[11] + a3 * b[15];
+
+ a0 = a[12];
+ a1 = a[13];
+ a2 = a[14];
+ a3 = a[15];
+ tmp[12] = a0 * b[0] + a1 * b[4] + a2 * b[8] + a3 * b[12];
+ tmp[13] = a0 * b[1] + a1 * b[5] + a2 * b[9] + a3 * b[13];
+ tmp[14] = a0 * b[2] + a1 * b[6] + a2 * b[10] + a3 * b[14];
+ tmp[15] = a0 * b[3] + a1 * b[7] + a2 * b[11] + a3 * b[15];
+ memcpy(m, tmp, sizeof(GLfloat) * 4 * 4);
+}
+
+void LoadIdentity(GLfloat* m) {
+ memset(m, 0, sizeof(GLfloat) * 4 * 4);
+ m[0] = m[5] = m[10] = m[15] = 1.0f;
+}
+
+} // namespace transform_4x4
+
diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/transforms.h b/native_client_sdk/src/examples/fullscreen_tumbler/transforms.h
new file mode 100644
index 0000000..5ac3d6e
--- /dev/null
+++ b/native_client_sdk/src/examples/fullscreen_tumbler/transforms.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2011 The Native Client Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXAMPLES_TUMBLER_TRANSFORMS_H_
+#define EXAMPLES_TUMBLER_TRANSFORMS_H_
+
+#include <GLES2/gl2.h>
+
+// A very simple set of 4x4 matrix routines. In all these routines, the input
+// matrix is assumed to be a 4x4 of GLfloats.
+
+namespace transform_4x4 {
+
+// Pre-multply |m| with a projection transformation 4x4 matrix from a
+// truncated pyramid viewing frustum.
+void Frustum(GLfloat* m,
+ GLfloat left,
+ GLfloat right,
+ GLfloat bottom,
+ GLfloat top,
+ GLfloat near_z,
+ GLfloat far_z);
+
+// Replace |m| with the 4x4 identity matrix.
+void LoadIdentity(GLfloat* m);
+
+// |m| <- |a| . |b|. |m| can point at the same memory as either |a| or |b|.
+void Multiply(GLfloat *m, GLfloat *a, GLfloat* b);
+
+// Pre-multiply |m| with a single-point perspective matrix based on the viewing
+// frustum whose view angle is |fovy|.
+void Perspective(GLfloat* m,
+ GLfloat fovy,
+ GLfloat aspect,
+ GLfloat near_z,
+ GLfloat far_z);
+
+// Pre-multiply |m| with a matrix that represents a translation by |tx|, |ty|,
+// |tz|.
+void Translate(GLfloat* m, GLfloat tx, GLfloat ty, GLfloat tz);
+} // namespace transform_4x4
+
+#endif // EXAMPLES_TUMBLER_TRANSFORMS_H_
+
diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/tumbler.cc b/native_client_sdk/src/examples/fullscreen_tumbler/tumbler.cc
new file mode 100644
index 0000000..3c38639
--- /dev/null
+++ b/native_client_sdk/src/examples/fullscreen_tumbler/tumbler.cc
@@ -0,0 +1,202 @@
+// Copyright (c) 2011 The Native Client 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 "examples/fullscreen_tumbler/tumbler.h"
+
+#include <stdio.h>
+
+#include <cstdlib>
+#include <cstring>
+#include <string>
+#include <vector>
+
+#include "examples/fullscreen_tumbler/cube.h"
+#include "examples/fullscreen_tumbler/opengl_context.h"
+#include "examples/fullscreen_tumbler/scripting_bridge.h"
+#include "ppapi/cpp/input_event.h"
+#include "ppapi/cpp/rect.h"
+#include "ppapi/cpp/size.h"
+#include "ppapi/cpp/var.h"
+
+namespace {
+const uint32_t kKeyEnter = 0x0D;
+const size_t kQuaternionElementCount = 4;
+const char* const kArrayStartCharacter = "[";
+const char* const kArrayEndCharacter = "]";
+const char* const kArrayDelimiter = ",";
+
+// Return the value of parameter named |param_name| from |parameters|. If
+// |param_name| doesn't exist, then return an empty string.
+std::string GetParameterNamed(
+ const std::string& param_name,
+ const tumbler::MethodParameter& parameters) {
+ tumbler::MethodParameter::const_iterator i =
+ parameters.find(param_name);
+ if (i == parameters.end()) {
+ return "";
+ }
+ return i->second;
+}
+
+// Convert the JSON string |array| into a vector of floats. |array| is
+// expected to be a string bounded by '[' and ']' and containing a
+// comma-delimited list of numbers. Any errors result in the return of an
+// empty array.
+std::vector<float> CreateArrayFromJSON(const std::string& json_array) {
+ std::vector<float> float_array;
+ size_t array_start_pos = json_array.find_first_of(kArrayStartCharacter);
+ size_t array_end_pos = json_array.find_last_of(kArrayEndCharacter);
+ if (array_start_pos == std::string::npos ||
+ array_end_pos == std::string::npos)
+ return float_array; // Malformed JSON: missing '[' or ']'.
+ // Pull out the array elements.
+ size_t token_pos = array_start_pos + 1;
+ while (token_pos < array_end_pos) {
+ float_array.push_back(strtof(json_array.data() + token_pos, NULL));
+ size_t delim_pos = json_array.find_first_of(kArrayDelimiter, token_pos);
+ if (delim_pos == std::string::npos)
+ break;
+ token_pos = delim_pos + 1;
+ }
+ return float_array;
+}
+} // namespace
+
+namespace tumbler {
+
+Tumbler::Tumbler(PP_Instance instance)
+ : pp::Instance(instance),
+ full_screen_(this),
+ has_focus_(false),
+ cube_(NULL) {
+ RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE);
+ RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_KEYBOARD);
+}
+
+Tumbler::~Tumbler() {
+ // Destroy the cube view while GL context is current.
+ opengl_context_->MakeContextCurrent(this);
+ delete cube_;
+}
+
+bool Tumbler::Init(uint32_t /* argc */,
+ const char* /* argn */[],
+ const char* /* argv */[]) {
+ // Add all the methods to the scripting bridge.
+ ScriptingBridge::SharedMethodCallbackExecutor set_orientation_method(
+ new tumbler::MethodCallback<Tumbler>(
+ this, &Tumbler::SetCameraOrientation));
+ scripting_bridge_.AddMethodNamed("setCameraOrientation",
+ set_orientation_method);
+ return true;
+}
+
+void Tumbler::HandleMessage(const pp::Var& message) {
+ if (!message.is_string())
+ return;
+ scripting_bridge_.InvokeMethod(message.AsString());
+}
+
+bool Tumbler::HandleInputEvent(const pp::InputEvent& event) {
+ switch (event.GetType()) {
+ case PP_INPUTEVENT_TYPE_UNDEFINED:
+ break;
+ case PP_INPUTEVENT_TYPE_MOUSEDOWN:
+ // If we do not yet have focus, return true. In return Chrome will give
+ // focus to the NaCl embed.
+ return !has_focus_;
+ break;
+ case PP_INPUTEVENT_TYPE_KEYDOWN:
+ HandleKeyDownEvent(pp::KeyboardInputEvent(event));
+ break;
+ case PP_INPUTEVENT_TYPE_MOUSEUP:
+ case PP_INPUTEVENT_TYPE_MOUSEMOVE:
+ case PP_INPUTEVENT_TYPE_MOUSEENTER:
+ case PP_INPUTEVENT_TYPE_MOUSELEAVE:
+ case PP_INPUTEVENT_TYPE_WHEEL:
+ case PP_INPUTEVENT_TYPE_RAWKEYDOWN:
+ case PP_INPUTEVENT_TYPE_KEYUP:
+ case PP_INPUTEVENT_TYPE_CHAR:
+ case PP_INPUTEVENT_TYPE_CONTEXTMENU:
+ case PP_INPUTEVENT_TYPE_IME_COMPOSITION_START:
+ case PP_INPUTEVENT_TYPE_IME_COMPOSITION_UPDATE:
+ case PP_INPUTEVENT_TYPE_IME_COMPOSITION_END:
+ case PP_INPUTEVENT_TYPE_IME_TEXT:
+ default:
+ return false;
+ }
+ return false;
+}
+
+void Tumbler::DidChangeView(const pp::Rect& position, const pp::Rect& clip) {
+ // Note: When switching to fullscreen, the new View position will be a
+ // rectangle that encompasses the entire screen - e.g. 1900x1200 - with its
+ // top-left corner at (0, 0). When switching back to the windowed screen the
+ // position returns to what it was before going to fullscreen.
+ int cube_width = cube_ ? cube_->width() : 0;
+ int cube_height = cube_ ? cube_->height() : 0;
+ if (position.size().width() == cube_width &&
+ position.size().height() == cube_height) {
+ return; // Size didn't change, no need to update anything.
+ }
+
+ if (opengl_context_ == NULL)
+ opengl_context_.reset(new OpenGLContext(this));
+ opengl_context_->InvalidateContext(this);
+ opengl_context_->ResizeContext(position.size());
+ if (!opengl_context_->MakeContextCurrent(this))
+ return;
+ if (cube_ == NULL) {
+ cube_ = new Cube(opengl_context_);
+ cube_->PrepareOpenGL();
+ }
+ cube_->Resize(position.size().width(), position.size().height());
+ DrawSelf();
+}
+
+void Tumbler::DidChangeFocus(bool focus) {
+ has_focus_ = focus;
+}
+
+void Tumbler::DrawSelf() {
+ if (cube_ == NULL || opengl_context_ == NULL)
+ return;
+ opengl_context_->MakeContextCurrent(this);
+ cube_->Draw();
+ opengl_context_->FlushContext();
+}
+
+void Tumbler::HandleKeyDownEvent(const pp::KeyboardInputEvent& key_event) {
+ // Pressing the Enter key toggles the view to/from full screen.
+ if (key_event.GetKeyCode() == kKeyEnter) {
+ if (!full_screen_.IsFullscreen()) {
+ if (!full_screen_.SetFullscreen(true)) {
+ printf("Failed to switch to fullscreen mode.\n");
+ }
+ } else {
+ if (!full_screen_.SetFullscreen(false)) {
+ printf("Failed to switch to normal mode.\n");
+ }
+ }
+ }
+}
+
+void Tumbler::SetCameraOrientation(
+ const tumbler::ScriptingBridge& bridge,
+ const tumbler::MethodParameter& parameters) {
+ // |parameters| is expected to contain one object named "orientation", whose
+ // value is a JSON string that represents an array of four floats.
+ if (parameters.size() != 1 || cube_ == NULL)
+ return;
+ std::string orientation_desc = GetParameterNamed("orientation", parameters);
+ std::vector<float> orientation = CreateArrayFromJSON(orientation_desc);
+ if (orientation.size() != kQuaternionElementCount) {
+ return;
+ }
+ cube_->SetOrientation(orientation);
+ DrawSelf();
+}
+
+} // namespace tumbler
+
diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/tumbler.h b/native_client_sdk/src/examples/fullscreen_tumbler/tumbler.h
new file mode 100644
index 0000000..d752ea3
--- /dev/null
+++ b/native_client_sdk/src/examples/fullscreen_tumbler/tumbler.h
@@ -0,0 +1,83 @@
+// Copyright (c) 2011 The Native Client Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXAMPLES_TUMBLER_TUMBLER_H_
+#define EXAMPLES_TUMBLER_TUMBLER_H_
+
+#include <pthread.h>
+#include <map>
+#include <vector>
+
+#include "examples/fullscreen_tumbler/cube.h"
+#include "examples/fullscreen_tumbler/opengl_context.h"
+#include "examples/fullscreen_tumbler/opengl_context_ptrs.h"
+#include "examples/fullscreen_tumbler/scripting_bridge.h"
+#include "ppapi/cpp/fullscreen.h"
+#include "ppapi/cpp/instance.h"
+
+namespace pp {
+class KeyboardInputEvent;
+} // namespace pp
+
+
+namespace tumbler {
+
+class Tumbler : public pp::Instance {
+ public:
+ explicit Tumbler(PP_Instance instance);
+
+ // The dtor makes the 3D context current before deleting the cube view, then
+ // destroys the 3D context both in the module and in the browser.
+ virtual ~Tumbler();
+
+ // Called by the browser when the NaCl module is loaded and all ready to go.
+ virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]);
+
+ // Called whenever the in-browser window changes size.
+ virtual void DidChangeView(const pp::Rect& position, const pp::Rect& clip);
+
+ // Called by the browser when the NaCl canvas gets or loses focus.
+ virtual void DidChangeFocus(bool has_focus);
+
+ // Called by the browser to handle the postMessage() call in Javascript.
+ virtual void HandleMessage(const pp::Var& message);
+
+ // Called by the browser to handle incoming input events.
+ virtual bool HandleInputEvent(const pp::InputEvent& event);
+
+ // Bind and publish the module's methods to JavaScript.
+ void InitializeMethods(ScriptingBridge* bridge);
+
+ // Set the camera orientation to the quaternion in |args[0]|. |args| must
+ // have length at least 1; the first element is expeted to be an Array
+ // object containing 4 floating point number elements (the quaternion).
+ // This method is bound to the JavaScript "setCameraOrientation" method and
+ // is called like this:
+ // module.setCameraOrientation([0.0, 1.0, 0.0, 0.0]);
+ void SetCameraOrientation(
+ const tumbler::ScriptingBridge& bridge,
+ const tumbler::MethodParameter& parameters);
+
+ // Called to draw the contents of the module's browser area.
+ void DrawSelf();
+
+ private:
+ // Process key-down input events.
+ void HandleKeyDownEvent(const pp::KeyboardInputEvent& key_event);
+
+ pp::Fullscreen full_screen_;
+ bool has_focus_;
+
+ // Browser connectivity and scripting support.
+ ScriptingBridge scripting_bridge_;
+
+ SharedOpenGLContext opengl_context_;
+ // Wouldn't it be awesome if we had boost::scoped_ptr<>?
+ Cube* cube_;
+};
+
+} // namespace tumbler
+
+#endif // EXAMPLES_TUMBLER_TUMBLER_H_
+
diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/tumbler.js b/native_client_sdk/src/examples/fullscreen_tumbler/tumbler.js
new file mode 100644
index 0000000..58096e4
--- /dev/null
+++ b/native_client_sdk/src/examples/fullscreen_tumbler/tumbler.js
@@ -0,0 +1,133 @@
+// Copyright (c) 2011 The Native Client Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview The tumbler Application object. This object instantiates a
+ * Trackball object and connects it to the element named |tumbler_content|.
+ * It also conditionally embeds a debuggable module or a release module into
+ * the |tumbler_content| element.
+ */
+
+// Requires tumbler
+// Requires tumbler.Dragger
+// Requires tumbler.Trackball
+
+/**
+ * Constructor for the Application class. Use the run() method to populate
+ * the object with controllers and wire up the events.
+ * @constructor
+ */
+tumbler.Application = function() {
+ /**
+ * The native module for the application. This refers to the module loaded
+ * using the <embed> tag.
+ * @type {Element}
+ * @private
+ */
+ this.module_ = null;
+
+ /**
+ * The trackball object.
+ * @type {tumbler.Trackball}
+ * @private
+ */
+ this.trackball_ = null;
+
+ /**
+ * The mouse-drag event object.
+ * @type {tumbler.Dragger}
+ * @private
+ */
+ this.dragger_ = null;
+
+ /**
+ * The function objects that get attached as event handlers. These are
+ * cached so that they can be removed when they are no longer needed.
+ * @type {function}
+ * @private
+ */
+ this.boundModuleDidLoad_ = null;
+}
+
+/**
+ * The ids used for elements in the DOM. The Tumbler Application expects these
+ * elements to exist.
+ * @enum {string}
+ * @private
+ */
+tumbler.Application.DomIds_ = {
+ MODULE: 'tumbler', // The <embed> element representing the NaCl module
+ VIEW: 'tumbler_view' // The <div> containing the NaCl element.
+}
+
+/**
+ * Called by the module loading function once the module has been loaded.
+ * @param {?Element} nativeModule The instance of the native module.
+ */
+tumbler.Application.prototype.moduleDidLoad = function() {
+ this.module_ = document.getElementById(tumbler.Application.DomIds_.MODULE);
+ // Unbind the load function.
+ this.boundModuleDidLoad_ = null;
+
+ /**
+ * Set the camera orientation property on the NaCl module.
+ * @param {Array.<number>} orientation A 4-element array representing the
+ * camera orientation as a quaternion.
+ */
+ this.module_.setCameraOrientation = function(orientation) {
+ var methodString = 'setCameraOrientation ' +
+ 'orientation:' +
+ JSON.stringify(orientation);
+ this.postMessage(methodString);
+ }
+
+ this.trackball_ = new tumbler.Trackball();
+ this.dragger_ = new tumbler.Dragger(this.module_);
+ this.dragger_.addDragListener(this.trackball_);
+}
+
+/**
+ * Asserts that cond is true; issues an alert and throws an Error otherwise.
+ * @param {bool} cond The condition.
+ * @param {String} message The error message issued if cond is false.
+ */
+tumbler.Application.prototype.assert = function(cond, message) {
+ if (!cond) {
+ message = "Assertion failed: " + message;
+ alert(message);
+ throw new Error(message);
+ }
+}
+
+/**
+ * The run() method starts and 'runs' the application. The trackball object
+ * is allocated and all the events get wired up.
+ * @param {?String} opt_contentDivName The id of a DOM element in which to
+ * embed the Native Client module. If unspecified, defaults to
+ * VIEW. The DOM element must exist.
+ */
+tumbler.Application.prototype.run = function(opt_contentDivName) {
+ contentDivName = opt_contentDivName || tumbler.Application.DomIds_.VIEW;
+ var contentDiv = document.getElementById(contentDivName);
+ this.assert(contentDiv, "Missing DOM element '" + contentDivName + "'");
+
+ // Note that the <EMBED> element is wrapped inside a <DIV>, which has a 'load'
+ // event listener attached. This method is used instead of attaching the
+ // 'load' event listener directly to the <EMBED> element to ensure that the
+ // listener is active before the NaCl module 'load' event fires.
+ this.boundModuleDidLoad_ = this.moduleDidLoad.bind(this);
+ contentDiv.addEventListener('load', this.boundModuleDidLoad_, true);
+
+ // Load the published .nexe. This includes the 'nacl' attribute which
+ // shows how to load multi-architecture modules. Each entry in the "nexes"
+ // object in the .nmf manifest file is a key-value pair: the key is the
+ // runtime ('x86-32', 'x86-64', etc.); the value is a URL for the desired
+ // NaCl module. To load the debug versions of your .nexes, set the 'nacl'
+ // attribute to the _dbg.nmf version of the manifest file.
+ contentDiv.innerHTML = '<embed id="'
+ + tumbler.Application.DomIds_.MODULE + '" '
+ + 'src=fullscreen_tumbler.nmf '
+ + 'type="application/x-nacl" '
+ + 'width="480" height="480" />'
+}
diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/tumbler_module.cc b/native_client_sdk/src/examples/fullscreen_tumbler/tumbler_module.cc
new file mode 100644
index 0000000..37e8bd0
--- /dev/null
+++ b/native_client_sdk/src/examples/fullscreen_tumbler/tumbler_module.cc
@@ -0,0 +1,45 @@
+// Copyright (c) 2011 The Native Client 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 "examples/fullscreen_tumbler/tumbler.h"
+#include "ppapi/cpp/instance.h"
+#include "ppapi/cpp/module.h"
+#include "ppapi/gles2/gl2ext_ppapi.h"
+
+/// The Module class. The browser calls the CreateInstance() method to create
+/// an instance of your NaCl module on the web page. The browser creates a new
+/// instance for each <embed> tag with type="application/x-nacl".
+class TumberModule : public pp::Module {
+ public:
+ TumberModule() : pp::Module() {}
+ virtual ~TumberModule() {
+ glTerminatePPAPI();
+ }
+
+ /// Called by the browser when the module is first loaded and ready to run.
+ /// This is called once per module, not once per instance of the module on
+ /// the page.
+ virtual bool Init() {
+ return glInitializePPAPI(get_browser_interface()) == GL_TRUE;
+ }
+
+ /// Create and return a Tumbler instance object.
+ /// @param[in] instance The browser-side instance.
+ /// @return the plugin-side instance.
+ virtual pp::Instance* CreateInstance(PP_Instance instance) {
+ return new tumbler::Tumbler(instance);
+ }
+};
+
+namespace pp {
+/// Factory function called by the browser when the module is first loaded.
+/// The browser keeps a singleton of this module. It calls the
+/// CreateInstance() method on the object you return to make instances. There
+/// is one instance per <embed> tag on the page. This is the main binding
+/// point for your NaCl module with the browser.
+Module* CreateModule() {
+ return new TumberModule();
+}
+} // namespace pp
+
diff --git a/native_client_sdk/src/examples/fullscreen_tumbler/vector3.js b/native_client_sdk/src/examples/fullscreen_tumbler/vector3.js
new file mode 100644
index 0000000..a79f781
--- /dev/null
+++ b/native_client_sdk/src/examples/fullscreen_tumbler/vector3.js
@@ -0,0 +1,91 @@
+// Copyright (c) 2011 The Native Client Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview A 3D vector class. Proviudes some utility functions on
+ * 3-dimentional vectors.
+ */
+
+// Requires tumbler
+
+/**
+ * Constructor for the Vector3 object. This class contains a 3-tuple that
+ * represents a vector in 3D space.
+ * @param {?number} opt_x The x-coordinate for this vector. If null or
+ * undefined, the x-coordinate value is set to 0.
+ * @param {?number} opt_y The y-coordinate for this vector. If null or
+ * undefined, the y-coordinate value is set to 0.
+ * @param {?number} opt_z The z-coordinate for this vector. If null or
+ * undefined, the z-coordinate value is set to 0.
+ * @constructor
+ */
+tumbler.Vector3 = function(opt_x, opt_y, opt_z) {
+ /**
+ * The vector's 3-tuple.
+ * @type {number}
+ */
+ this.x = opt_x || 0;
+ this.y = opt_y || 0;
+ this.z = opt_z || 0;
+}
+
+/**
+ * Method to return the magnitude of a Vector3.
+ * @return {number} the magnitude of the vector.
+ */
+tumbler.Vector3.prototype.magnitude = function() {
+ return Math.sqrt(this.dot(this));
+}
+
+/**
+ * Normalize the vector in-place.
+ * @return {number} the magnitude of the vector.
+ */
+tumbler.Vector3.prototype.normalize = function() {
+ var mag = this.magnitude();
+ if (mag < tumbler.Vector3.DOUBLE_EPSILON)
+ return 0.0; // |this| is equivalent to the 0-vector, don't normalize.
+ this.scale(1.0 / mag);
+ return mag;
+}
+
+/**
+ * Scale the vector in-place by |s|.
+ * @param {!number} s The scale factor.
+ */
+tumbler.Vector3.prototype.scale = function(s) {
+ this.x *= s;
+ this.y *= s;
+ this.z *= s;
+}
+
+/**
+ * Compute the dot product: |this| . v.
+ * @param {!tumbler.Vector3} v The vector to dot.
+ * @return {number} the result of |this| . v.
+ */
+tumbler.Vector3.prototype.dot = function(v) {
+ return this.x * v.x + this.y * v.y + this.z * v.z;
+}
+
+/**
+ * Compute the cross product: |this| X v.
+ * @param {!tumbler.Vector3} v The vector to cross with.
+ * @return {tumbler.Vector3} the result of |this| X v.
+ */
+tumbler.Vector3.prototype.cross = function(v) {
+ var vCross = new tumbler.Vector3(this.y * v.z - this.z * v.y,
+ this.z * v.x - this.x * v.z,
+ this.x * v.y - this.y * v.x);
+ return vCross;
+}
+
+/**
+ * Real numbers that are less than this distance apart are considered
+ * equivalent.
+ * TODO(dspringer): It seems as though there should be a const like this
+ * in generally available somewhere.
+ * @type {number}
+ */
+tumbler.Vector3.DOUBLE_EPSILON = 1.0e-16;
diff --git a/native_client_sdk/src/examples/geturl/build.scons b/native_client_sdk/src/examples/geturl/build.scons
new file mode 100644
index 0000000..f855644
--- /dev/null
+++ b/native_client_sdk/src/examples/geturl/build.scons
@@ -0,0 +1,41 @@
+#! -*- python -*-
+#
+# Copyright (c) 2011 The Native Client Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import make_nacl_env
+import os
+
+nacl_env = make_nacl_env.NaClEnvironment(
+ use_c_plus_plus_libs=True, nacl_platform=os.getenv('NACL_TARGET_PLATFORM'),
+ install_subdir='geturl', lib_prefix='..')
+nacl_env.Append(
+ # Add a CPPPATH that enables the full-path #include directives, such as
+ # #include "examples/sine_synth/sine_synth.h"
+ CPPPATH=[os.path.dirname(os.path.dirname(os.getcwd()))],
+ # Strict ANSI compliance.
+ EXTRA_CCFLAGS=['-pedantic'],
+ )
+
+sources = ['geturl.cc', 'geturl_handler.cc']
+
+opt_nexes, dbg_nexes = nacl_env.AllNaClModules(sources, 'geturl')
+
+# This target is used by the SDK build system to provide a prebuilt version
+# of the example in the SDK installer.
+nacl_env.InstallPrebuilt('geturl')
+
+app_files = [
+ 'geturl.html',
+ 'geturl_success.html',
+ 'geturl.nmf',
+ ]
+
+# Split the install of the .nexes from the other app sources so that the strip
+# action is applied to the .nexes only.
+install_nexes = nacl_env.NaClStrippedInstall(dir=nacl_env['NACL_INSTALL_ROOT'],
+ source=opt_nexes)
+install_app = nacl_env.Install(dir=nacl_env['NACL_INSTALL_ROOT'],
+ source=app_files)
+nacl_env.Alias('install', source=install_app + install_nexes)
diff --git a/native_client_sdk/src/examples/geturl/geturl.cc b/native_client_sdk/src/examples/geturl/geturl.cc
new file mode 100644
index 0000000..48d5cbb
--- /dev/null
+++ b/native_client_sdk/src/examples/geturl/geturl.cc
@@ -0,0 +1,96 @@
+// Copyright (c) 2011 The Native Client Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This example demonstrates how to load content of the page into NaCl module.
+
+#include <cstdio>
+#include <string>
+#include "examples/geturl/geturl_handler.h"
+#include "ppapi/cpp/instance.h"
+#include "ppapi/cpp/url_loader.h"
+#include "ppapi/cpp/module.h"
+#include "ppapi/cpp/var.h"
+
+// These are the method names as JavaScript sees them.
+namespace {
+const char* const kLoadUrlMethodId = "getUrl";
+static const char kMessageArgumentSeparator = ':';
+
+// Exception strings. These are passed back to the browser when errors
+// happen during property accesses or method calls.
+const char* const kExceptionStartFailed = "GetURLHandler::Start() failed";
+const char* const kExceptionURLNotAString = "URL is not a string";
+} // namespace
+
+// The Instance class. One of these exists for each instance of your NaCl
+// module on the web page. The browser will ask the Module object to create
+// a new Instance for each occurrence of the <embed> tag that has these
+// attributes:
+// type="application/x-nacl"
+// src="geturl.nmf"
+class GetURLInstance : public pp::Instance {
+ public:
+ explicit GetURLInstance(PP_Instance instance) : pp::Instance(instance) {}
+ virtual ~GetURLInstance() {}
+
+ // Called by the browser to handle the postMessage() call in Javascript.
+ // The message in this case is expected to contain the string 'getUrl'
+ // followed by a ':' separator, then the URL to fetch. If a valid message
+ // of the form 'getUrl:URL' is received, then start up an asynchronous
+ // download of URL. In the event that errors occur, this method posts an
+ // error string back to the browser.
+ virtual void HandleMessage(const pp::Var& var_message);
+};
+
+void GetURLInstance::HandleMessage(const pp::Var& var_message) {
+ if (!var_message.is_string()) {
+ return;
+ }
+ std::string message = var_message.AsString();
+ if (message.find(kLoadUrlMethodId) == 0) {
+ // The argument to getUrl is everything after the first ':'.
+ size_t sep_pos = message.find_first_of(kMessageArgumentSeparator);
+ if (sep_pos != std::string::npos) {
+ std::string url = message.substr(sep_pos + 1);
+ printf("GetURLInstance::HandleMessage('%s', '%s')\n",
+ message.c_str(),
+ url.c_str());
+ fflush(stdout);
+ GetURLHandler* handler = GetURLHandler::Create(this, url);
+ if (handler != NULL) {
+ // Starts asynchronous download. When download is finished or when an
+ // error occurs, |handler| posts the results back to the browser
+ // vis PostMessage and self-destroys.
+ handler->Start();
+ }
+ }
+ }
+}
+
+
+// The Module class. The browser calls the CreateInstance() method to create
+// an instance of you NaCl module on the web page. The browser creates a new
+// instance for each <embed> tag with type="application/x-nacl".
+class GetURLModule : public pp::Module {
+ public:
+ GetURLModule() : pp::Module() {}
+ virtual ~GetURLModule() {}
+
+ // Create and return a GetURLInstance object.
+ virtual pp::Instance* CreateInstance(PP_Instance instance) {
+ return new GetURLInstance(instance);
+ }
+};
+
+// Factory function called by the browser when the module is first loaded.
+// The browser keeps a singleton of this module. It calls the
+// CreateInstance() method on the object you return to make instances. There
+// is one instance per <embed> tag on the page. This is the main binding
+// point for your NaCl module with the browser.
+namespace pp {
+Module* CreateModule() {
+ return new GetURLModule();
+}
+} // namespace pp
+
diff --git a/native_client_sdk/src/examples/geturl/geturl.html b/native_client_sdk/src/examples/geturl/geturl.html
new file mode 100644
index 0000000..c4a0c79
--- /dev/null
+++ b/native_client_sdk/src/examples/geturl/geturl.html
@@ -0,0 +1,125 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+ <!--
+ Copyright (c) 2011 The Native Client Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style license that can be
+ found in the LICENSE file.
+ -->
+<head>
+ <title>Get URL</title>
+
+ <script type="text/javascript">
+ geturlModule = null; // Global application object.
+ statusText = 'NO-STATUS';
+
+ // Indicate success when the NaCl module has loaded.
+ function moduleDidLoad() {
+ geturlModule = document.getElementById('geturl');
+ updateStatus('SUCCESS');
+ }
+
+ // If the page loads before the Native Client module loads, then set the
+ // status message indicating that the module is still loading. Otherwise,
+ // do not change the status message.
+ function pageDidLoad() {
+ if (geturl == null) {
+ updateStatus('LOADING...');
+ } else {
+ // It's possible that the Native Client module onload event fired
+ // before the page's onload event. In this case, the status message
+ // will reflect 'SUCCESS', but won't be displayed. This call will
+ // display the current message.
+ updateStatus();
+ }
+ }
+
+ // Called from the NaCl module via PostMessage(). The message data
+ // contains a URL followed by a '\n' separator character and the result
+ // text. The result test itself can contain '\n' characters, only the first
+ // '\n' is considered when separating the message parameters.
+ function handleMessage(message_event) {
+ var logElt = document.getElementById('general_output');
+ // Find the first line break. This separates the URL data from the
+ // result text. Note that the result text can contain any number of
+ // '\n' characters, so split() won't work here.
+ var url = message_event.data;
+ var result = '';
+ var eol_pos = message_event.data.indexOf("\n");
+ if (eol_pos != -1) {
+ url = message_event.data.substring(0, eol_pos);
+ if (eol_pos < message_event.data.length - 1) {
+ result = message_event.data.substring(eol_pos + 1);
+ }
+ }
+ logElt.textContent += 'FULLY QUALIFIED URL: ' + url + '\n';
+ logElt.textContent += 'RESULT:\n' + result + '\n';
+ }
+
+ function loadUrl() {
+ geturlModule.postMessage('getUrl:geturl_success.html');
+ }
+
+ // Set the global status message. If the element with id 'statusField'
+ // exists, then set its HTML to the status message as well.
+ // @param opt_message The message text. If this is null or undefined, then
+ // attempt to set the element with id 'status_field' to the value of
+ // @a statusText.
+ function updateStatus(opt_message) {
+ if (opt_message)
+ statusText = opt_message;
+ var statusField = document.getElementById('status_field');
+ if (statusField) {
+ statusField.innerHTML = statusText;
+ }
+ }
+ </script>
+</head>
+<body onload="pageDidLoad()">
+
+<h1>Native Client GetURL Module</h1>
+<p>
+<table border=5 cellpadding=5% summary="A title and a result log">
+ <tr>
+ <td valign=top><pre id='general_output' class='notrun'></pre></td>
+ </tr>
+</table>
+
+ <form name="geturl_form" action="" method="get">
+ <input type="button" value="Get URL" onclick="loadUrl()"/>
+ </form>
+
+ <!-- Load the published .nexe. This includes the 'src' attribute which
+ shows how to load multi-architecture modules. Each entry in the "nexes"
+ object in the .nmf manifest file is a key-value pair: the key is the runtime
+ ('x86-32', 'x86-64', etc.); the value is a URL for the desired NaCl module.
+ To load the debug versions of your .nexes, set the 'src' attribute to the
+ _dbg.nmf version of the manifest file.
+
+ Note: The <EMBED> element is wrapped inside a <DIV>, which has both a 'load'
+ and a 'message' event listener attached. This wrapping method is used
+ instead of attaching the event listeners directly to the <EMBED> element to
+ ensure that the listeners are active before the NaCl module 'load' event
+ fires. This also allows you to use PPB_Messaging.PostMessage() (in C) or
+ pp::Instance.PostMessage() (in C++) from within the initialization code in
+ your NaCl module.
+ -->
+ <div id="listener">
+ <script type="text/javascript">
+ var listener = document.getElementById('listener')
+ listener.addEventListener('load', moduleDidLoad, true);
+ listener.addEventListener('message', handleMessage, true);
+ </script>
+
+ <embed name="nacl_module"
+ id="geturl"
+ width=0 height=0
+ src="geturl.nmf"
+ type="application/x-nacl" />
+ </div>
+</p>
+
+<h2>Module loading status</h2>
+<div id="status_field">NO-STATUS</div>
+</body>
+</html>
diff --git a/native_client_sdk/src/examples/geturl/geturl_handler.cc b/native_client_sdk/src/examples/geturl/geturl_handler.cc
new file mode 100644
index 0000000..f6536fc
--- /dev/null
+++ b/native_client_sdk/src/examples/geturl/geturl_handler.cc
@@ -0,0 +1,137 @@
+// Copyright (c) 2011 The Native Client 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 "examples/geturl/geturl_handler.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include "ppapi/c/pp_errors.h"
+#include "ppapi/c/ppb_instance.h"
+#include "ppapi/cpp/module.h"
+#include "ppapi/cpp/var.h"
+
+namespace {
+bool IsError(int32_t result) {
+ return ((PP_OK != result) && (PP_OK_COMPLETIONPENDING != result));
+}
+} // namespace
+
+GetURLHandler* GetURLHandler::Create(pp::Instance* instance,
+ const std::string& url) {
+ return new GetURLHandler(instance, url);
+}
+
+GetURLHandler::GetURLHandler(pp::Instance* instance,
+ const std::string& url)
+ : instance_(instance),
+ url_(url),
+ url_request_(instance),
+ url_loader_(instance),
+ cc_factory_(this) {
+ url_request_.SetURL(url);
+ url_request_.SetMethod("GET");
+}
+
+GetURLHandler::~GetURLHandler() {
+}
+
+void GetURLHandler::Start() {
+ pp::CompletionCallback cc =
+ cc_factory_.NewRequiredCallback(&GetURLHandler::OnOpen);
+ url_loader_.Open(url_request_, cc);
+}
+
+void GetURLHandler::OnOpen(int32_t result) {
+ if (result != PP_OK) {
+ ReportResultAndDie(url_, "pp::URLLoader::Open() failed", false);
+ return;
+ }
+ // Here you would process the headers. A real program would want to at least
+ // check the HTTP code and potentially cancel the request.
+ // pp::URLResponseInfo response = loader_.GetResponseInfo();
+
+ // Start streaming.
+ ReadBody();
+}
+
+void GetURLHandler::AppendDataBytes(const char* buffer, int32_t num_bytes) {
+ if (num_bytes <= 0)
+ return;
+ // Make sure we don't get a buffer overrun.
+ num_bytes = std::min(READ_BUFFER_SIZE, num_bytes);
+ url_response_body_.reserve(url_response_body_.size() + num_bytes);
+ url_response_body_.insert(url_response_body_.end(),
+ buffer,
+ buffer + num_bytes);
+}
+
+void GetURLHandler::OnRead(int32_t result) {
+ if (result == PP_OK) {
+ // Streaming the file is complete.
+ ReportResultAndDie(url_, url_response_body_, true);
+ } else if (result > 0) {
+ // The URLLoader just filled "result" number of bytes into our buffer.
+ // Save them and perform another read.
+ AppendDataBytes(buffer_, result);
+ ReadBody();
+ } else {
+ // A read error occurred.
+ ReportResultAndDie(url_,
+ "pp::URLLoader::ReadResponseBody() result<0",
+ false);
+ }
+}
+
+void GetURLHandler::ReadBody() {
+ // Note that you specifically want an "optional" callback here. This will
+ // allow ReadBody() to return synchronously, ignoring your completion
+ // callback, if data is available. For fast connections and large files,
+ // reading as fast as we can will make a large performance difference
+ // However, in the case of a synchronous return, we need to be sure to run
+ // the callback we created since the loader won't do anything with it.
+ pp::CompletionCallback cc =
+ cc_factory_.NewOptionalCallback(&GetURLHandler::OnRead);
+ int32_t result = PP_OK;
+ do {
+ result = url_loader_.ReadResponseBody(buffer_, sizeof(buffer_), cc);
+ // Handle streaming data directly. Note that we *don't* want to call
+ // OnRead here, since in the case of result > 0 it will schedule
+ // another call to this function. If the network is very fast, we could
+ // end up with a deeply recursive stack.
+ if (result > 0) {
+ AppendDataBytes(buffer_, result);
+ }
+ } while (result > 0);
+
+ if (result != PP_OK_COMPLETIONPENDING) {
+ // Either we reached the end of the stream (result == PP_OK) or there was
+ // an error. We want OnRead to get called no matter what to handle
+ // that case, whether the error is synchronous or asynchronous. If the
+ // result code *is* COMPLETIONPENDING, our callback will be called
+ // asynchronously.
+ cc.Run(result);
+ }
+}
+
+void GetURLHandler::ReportResultAndDie(const std::string& fname,
+ const std::string& text,
+ bool success) {
+ ReportResult(fname, text, success);
+ delete this;
+}
+
+void GetURLHandler::ReportResult(const std::string& fname,
+ const std::string& text,
+ bool success) {
+ if (success)
+ printf("GetURLHandler::ReportResult(Ok).\n");
+ else
+ printf("GetURLHandler::ReportResult(Err). %s\n", text.c_str());
+ fflush(stdout);
+ if (instance_) {
+ pp::Var var_result(fname + "\n" + text);
+ instance_->PostMessage(var_result);
+ }
+}
+
diff --git a/native_client_sdk/src/examples/geturl/geturl_handler.h b/native_client_sdk/src/examples/geturl/geturl_handler.h
new file mode 100644
index 0000000..928f22c
--- /dev/null
+++ b/native_client_sdk/src/examples/geturl/geturl_handler.h
@@ -0,0 +1,81 @@
+// Copyright (c) 2011 The Native Client Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXAMPLES_GETURL_GETURL_HANDLER_H_
+#define EXAMPLES_GETURL_GETURL_HANDLER_H_
+
+#include <string>
+#include "ppapi/cpp/completion_callback.h"
+#include "ppapi/cpp/url_loader.h"
+#include "ppapi/cpp/url_request_info.h"
+#include "ppapi/cpp/instance.h"
+
+#define READ_BUFFER_SIZE 4096
+
+// GetURLHandler is used to download data from |url|. When download is
+// finished or when an error occurs, it posts a message back to the browser
+// with the results encoded in the message as a string and self-destroys.
+//
+// EXAMPLE USAGE:
+// GetURLHandler* handler* = GetURLHandler::Create(instance,url);
+// handler->Start();
+//
+class GetURLHandler {
+ public:
+ // Creates instance of GetURLHandler on the heap.
+ // GetURLHandler objects shall be created only on the heap (they
+ // self-destroy when all data is in).
+ static GetURLHandler* Create(pp::Instance* instance_,
+ const std::string& url);
+ // Initiates page (URL) download.
+ void Start();
+
+ private:
+ GetURLHandler(pp::Instance* instance_, const std::string& url);
+ ~GetURLHandler();
+
+ // Callback fo the pp::URLLoader::Open().
+ // Called by pp::URLLoader when response headers are received or when an
+ // error occurs (in response to the call of pp::URLLoader::Open()).
+ // Look at <ppapi/c/ppb_url_loader.h> and
+ // <ppapi/cpp/url_loader.h> for more information about pp::URLLoader.
+ void OnOpen(int32_t result);
+
+ // Callback fo the pp::URLLoader::ReadResponseBody().
+ // |result| contains the number of bytes read or an error code.
+ // Appends data from this->buffer_ to this->url_response_body_.
+ void OnRead(int32_t result);
+
+ // Reads the response body (asynchronously) into this->buffer_.
+ // OnRead() will be called when bytes are received or when an error occurs.
+ void ReadBody();
+
+ // Append data bytes read from the URL onto the internal buffer. Does
+ // nothing if |num_bytes| is 0.
+ void AppendDataBytes(const char* buffer, int32_t num_bytes);
+
+ // Post a message back to the browser with the download results.
+ void ReportResult(const std::string& fname,
+ const std::string& text,
+ bool success);
+ // Post a message back to the browser with the download results and
+ // self-destroy. |this| is no longer valid when this method returns.
+ void ReportResultAndDie(const std::string& fname,
+ const std::string& text,
+ bool success);
+
+ pp::Instance* instance_; // Weak pointer.
+ std::string url_; // URL to be downloaded.
+ pp::URLRequestInfo url_request_;
+ pp::URLLoader url_loader_; // URLLoader provides an API to download URLs.
+ char buffer_[READ_BUFFER_SIZE]; // Temporary buffer for reads.
+ std::string url_response_body_; // Contains accumulated downloaded data.
+ pp::CompletionCallbackFactory<GetURLHandler> cc_factory_;
+
+ GetURLHandler(const GetURLHandler&);
+ void operator=(const GetURLHandler&);
+};
+
+#endif // EXAMPLES_GETURL_GETURL_HANDLER_H_
+
diff --git a/native_client_sdk/src/examples/geturl/geturl_success.html b/native_client_sdk/src/examples/geturl/geturl_success.html
new file mode 100644
index 0000000..8f2f112
--- /dev/null
+++ b/native_client_sdk/src/examples/geturl/geturl_success.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+ <!--
+ Copyright (c) 2010 The Native Client Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style license that can be
+ found in the LICENSE file.
+ -->
+ <head>
+ <title>PPAPI geturl example</title>
+ <META HTTP-EQUIV="Pragma" CONTENT="no-cache" />
+ <META HTTP-EQUIV="Expires" CONTENT="-1" />
+ </head>
+ <body>
+ <h1>PPAPI geturl example</h1>
+ The PPAPI geturl example fetches the contents of this page.
+ If you are seeing the contents of this page as part of the test output,
+ then the test passed.
+ </body>
+</html>
diff --git a/native_client_sdk/src/examples/hello_world/build.scons b/native_client_sdk/src/examples/hello_world/build.scons
new file mode 100644
index 0000000..51dc5df
--- /dev/null
+++ b/native_client_sdk/src/examples/hello_world/build.scons
@@ -0,0 +1,59 @@
+#! -*- python -*-
+#
+# Copyright (c) 2011 The Native Client Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import make_nacl_env
+import nacl_utils
+import os
+
+nacl_env = make_nacl_env.NaClEnvironment(
+ use_c_plus_plus_libs=True, nacl_platform=os.getenv('NACL_TARGET_PLATFORM'),
+ install_subdir='hello_world', lib_prefix='..')
+nacl_test_env = make_nacl_env.NaClEnvironment(
+ use_c_plus_plus_libs=True,
+ nacl_platform=os.getenv('NACL_TARGET_PLATFORM'),
+ use_ppapi=False)
+for env in [nacl_env, nacl_test_env]:
+ env.Append(
+ # Add a CPPPATH that enables the full-path #include directives, such as
+ # #include "examples/sine_synth/sine_synth.h"
+ CPPPATH=[os.path.dirname(os.path.dirname(os.getcwd()))],
+ # Strict ANSI compliance.
+ CCFLAGS=['-pedantic', '-Werror'],
+ )
+
+sources = ['hello_world.cc', 'helper_functions.cc']
+test_sources = ['helper_functions.cc', 'test_helper_functions.cc']
+
+opt_nexes, dbg_nexes = nacl_env.AllNaClModules(sources, 'hello_world')
+
+nacl_test_32 = nacl_test_env.Clone()
+nacl_test_32.NaClTestProgram(test_sources,
+ nacl_utils.ARCH_SPECS['x86-32'],
+ module_name='hello_world_test',
+ target_name='test32')
+
+nacl_test_64 = nacl_test_env.Clone()
+nacl_test_64.NaClTestProgram(test_sources,
+ nacl_utils.ARCH_SPECS['x86-64'],
+ module_name='hello_world_test',
+ target_name='test64')
+
+# This target is used by the SDK build system to provide a prebuilt version
+# of the example in the SDK installer.
+nacl_env.InstallPrebuilt('hello_world')
+
+app_files = [
+ 'hello_world.html',
+ 'hello_world.nmf',
+ ]
+
+# Split the install of the .nexes from the other app sources so that the strip
+# action is applied to the .nexes only.
+install_nexes = nacl_env.NaClStrippedInstall(dir=nacl_env['NACL_INSTALL_ROOT'],
+ source=opt_nexes)
+install_app = nacl_env.Install(dir=nacl_env['NACL_INSTALL_ROOT'],
+ source=app_files)
+nacl_env.Alias('install', source=install_app + install_nexes)
diff --git a/native_client_sdk/src/examples/hello_world/hello_world.cc b/native_client_sdk/src/examples/hello_world/hello_world.cc
new file mode 100644
index 0000000..df67961
--- /dev/null
+++ b/native_client_sdk/src/examples/hello_world/hello_world.cc
@@ -0,0 +1,130 @@
+// Copyright (c) 2011 The Native Client Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/// @file
+/// This example demonstrates loading, running and scripting a very simple NaCl
+/// module. To load the NaCl module, the browser first looks for the
+/// CreateModule() factory method (at the end of this file). It calls
+/// CreateModule() once to load the module code from your .nexe. After the
+/// .nexe code is loaded, CreateModule() is not called again.
+///
+/// Once the .nexe code is loaded, the browser then calls the
+/// HelloWorldModule::CreateInstance()
+/// method on the object returned by CreateModule(). It calls CreateInstance()
+/// each time it encounters an <embed> tag that references your NaCl module.
+
+#include <cstdio>
+#include <cstring>
+#include <string>
+#include "examples/hello_world/helper_functions.h"
+#include "ppapi/cpp/instance.h"
+#include "ppapi/cpp/module.h"
+#include "ppapi/cpp/var.h"
+
+namespace hello_world {
+/// Method name for ReverseText, as seen by JavaScript code.
+const char* const kReverseTextMethodId = "reverseText";
+
+/// Method name for FortyTwo, as seen by Javascript code. @see FortyTwo()
+const char* const kFortyTwoMethodId = "fortyTwo";
+
+/// Separator character for the reverseText method.
+static const char kMessageArgumentSeparator = ':';
+
+/// This is the module's function that invokes FortyTwo and converts the return
+/// value from an int32_t to a pp::Var for return.
+pp::Var MarshallFortyTwo() {
+ return pp::Var(FortyTwo());
+}
+
+/// This function is passed the arg list from the JavaScript call to
+/// @a reverseText.
+/// It makes sure that there is one argument and that it is a string, returning
+/// an error message if it is not.
+/// On good input, it calls ReverseText and returns the result. The result is
+/// then sent back via a call to PostMessage.
+pp::Var MarshallReverseText(const std::string& text) {
+ return pp::Var(ReverseText(text));
+}
+
+/// The Instance class. One of these exists for each instance of your NaCl
+/// module on the web page. The browser will ask the Module object to create
+/// a new Instance for each occurrence of the <embed> tag that has these
+/// attributes:
+/// <pre>
+/// type="application/x-nacl"
+/// nacl="hello_world.nmf"
+/// </pre>
+class HelloWorldInstance : public pp::Instance {
+ public:
+ explicit HelloWorldInstance(PP_Instance instance) : pp::Instance(instance) {}
+ virtual ~HelloWorldInstance() {}
+
+ /// Called by the browser to handle the postMessage() call in Javascript.
+ /// Detects which method is being called from the message contents, and
+ /// calls the appropriate function. Posts the result back to the browser
+ /// asynchronously.
+ /// @param[in] var_message The message posted by the browser. The possible
+ /// messages are 'fortyTwo' and 'reverseText:Hello World'. Note that
+ /// the 'reverseText' form contains the string to reverse following a ':'
+ /// separator.
+ virtual void HandleMessage(const pp::Var& var_message);
+};
+
+void HelloWorldInstance::HandleMessage(const pp::Var& var_message) {
+ if (!var_message.is_string()) {
+ return;
+ }
+ std::string message = var_message.AsString();
+ pp::Var return_var;
+ if (message == kFortyTwoMethodId) {
+ // Note that no arguments are passed in to FortyTwo.
+ return_var = MarshallFortyTwo();
+ } else if (message.find(kReverseTextMethodId) == 0) {
+ // The argument to reverseText is everything after the first ':'.
+ size_t sep_pos = message.find_first_of(kMessageArgumentSeparator);
+ if (sep_pos != std::string::npos) {
+ std::string string_arg = message.substr(sep_pos + 1);
+ return_var = MarshallReverseText(string_arg);
+ }
+ }
+ // Post the return result back to the browser. Note that HandleMessage() is
+ // always called on the main thread, so it's OK to post the return message
+ // directly from here. The return post is asynhronous: PostMessage returns
+ // immediately.
+ PostMessage(return_var);
+}
+
+/// The Module class. The browser calls the CreateInstance() method to create
+/// an instance of your NaCl module on the web page. The browser creates a new
+/// instance for each <embed> tag with
+/// <code>type="application/x-nacl"</code>.
+class HelloWorldModule : public pp::Module {
+ public:
+ HelloWorldModule() : pp::Module() {}
+ virtual ~HelloWorldModule() {}
+
+ /// Create and return a HelloWorldInstance object.
+ /// @param[in] instance a handle to a plug-in instance.
+ /// @return a newly created HelloWorldInstance.
+ /// @note The browser is responsible for calling @a delete when done.
+ virtual pp::Instance* CreateInstance(PP_Instance instance) {
+ return new HelloWorldInstance(instance);
+ }
+};
+} // namespace hello_world
+
+
+namespace pp {
+/// Factory function called by the browser when the module is first loaded.
+/// The browser keeps a singleton of this module. It calls the
+/// CreateInstance() method on the object you return to make instances. There
+/// is one instance per <embed> tag on the page. This is the main binding
+/// point for your NaCl module with the browser.
+/// @return new HelloWorldModule.
+/// @note The browser is responsible for deleting returned @a Module.
+Module* CreateModule() {
+ return new hello_world::HelloWorldModule();
+}
+} // namespace pp
diff --git a/native_client_sdk/src/examples/hello_world/hello_world.html b/native_client_sdk/src/examples/hello_world/hello_world.html
new file mode 100644
index 0000000..16dc42c
--- /dev/null
+++ b/native_client_sdk/src/examples/hello_world/hello_world.html
@@ -0,0 +1,126 @@
+<!DOCTYPE html>
+<html>
+ <!--
+ Copyright (c) 2011 The Native Client Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style license that can be
+ found in the LICENSE file.
+ -->
+<head>
+ <title>Hello, World!</title>
+
+ <script type="text/javascript">
+ helloWorldModule = null; // Global application object.
+ statusText = 'NO-STATUS';
+
+ // Indicate success when the NaCl module has loaded.
+ function moduleDidLoad() {
+ helloWorldModule = document.getElementById('hello_world');
+ updateStatus('SUCCESS');
+ }
+
+ // Handle a message coming from the NaCl module.
+ function handleMessage(message_event) {
+ alert(message_event.data);
+ }
+
+ // If the page loads before the Native Client module loads, then set the
+ // status message indicating that the module is still loading. Otherwise,
+ // do not change the status message.
+ function pageDidLoad() {
+ // Set the focus on the text input box. Doing this means you can press
+ // return as soon as the page loads, and it will fire the reversetText()
+ // function.
+ document.forms.helloForm.inputBox.focus();
+ if (helloWorldModule == null) {
+ updateStatus('LOADING...');
+ } else {
+ // It's possible that the Native Client module onload event fired
+ // before the page's onload event. In this case, the status message
+ // will reflect 'SUCCESS', but won't be displayed. This call will
+ // display the current message.
+ updateStatus();
+ }
+ }
+
+ function fortyTwo() {
+ helloWorldModule.postMessage('fortyTwo');
+ }
+
+ function reverseText() {
+ // Grab the text from the text box, pass it into reverseText()
+ var inputBox = document.forms.helloForm.inputBox;
+ helloWorldModule.postMessage('reverseText:' + inputBox.value);
+ // Note: a |false| return tells the <form> tag to cancel the GET action
+ // when submitting the form.
+ return false;
+ }
+
+ // Set the global status message. If the element with id 'statusField'
+ // exists, then set its HTML to the status message as well.
+ // opt_message The message test. If this is null or undefined, then
+ // attempt to set the element with id 'statusField' to the value of
+ // |statusText|.
+ function updateStatus(opt_message) {
+ if (opt_message)
+ statusText = opt_message;
+ var statusField = document.getElementById('statusField');
+ if (statusField) {
+ statusField.innerHTML = statusText;
+ }
+ }
+ </script>
+</head>
+<body onload="pageDidLoad()">
+
+<h1>Native Client Simple Module</h1>
+<p>
+ <form name="helloForm"
+ action=""
+ method="get"
+ onsubmit="return reverseText()">
+ <input type="text" id="inputBox" name="inputBox" value="Hello world" /><p/>
+ <input type="button" value="Call fortyTwo()" onclick="fortyTwo()" />
+ <input type="submit" value="Call reverseText()" />
+ </form>
+ <!-- Load the published .nexe. This includes the 'src' attribute which
+ shows how to load multi-architecture modules. Each entry in the "nexes"
+ object in the .nmf manifest file is a key-value pair: the key is the runtime
+ ('x86-32', 'x86-64', etc.); the value is a URL for the desired NaCl module.
+ To load the debug versions of your .nexes, set the 'src' attribute to the
+ _dbg.nmf version of the manifest file.
+
+ Note: The <EMBED> element is wrapped inside a <DIV>, which has both a 'load'
+ and a 'message' event listener attached. This wrapping method is used
+ instead of attaching the event listeners directly to the <EMBED> element to
+ ensure that the listeners are active before the NaCl module 'load' event
+ fires. This also allows you to use PPB_Messaging.PostMessage() (in C) or
+ pp::Instance.PostMessage() (in C++) from within the initialization code in
+ your NaCl module.
+ -->
+ <div id="listener">
+ <script type="text/javascript">
+ var listener = document.getElementById('listener')
+ listener.addEventListener('load', moduleDidLoad, true);
+ listener.addEventListener('message', handleMessage, true);
+ </script>
+
+ <embed name="nacl_module"
+ id="hello_world"
+ width=0 height=0
+ src="hello_world.nmf"
+ type="application/x-nacl" />
+ </div>
+
+</p>
+
+<p>If the module is working correctly, a click on the "Call fortyTwo()" button
+ should open a popup dialog containing <b>42</b> as its value.</p>
+
+<p> Clicking on the "Call reverseText()" button
+ should open a popup dialog containing the textbox contents and its reverse
+ as its value.</p>
+
+<h2>Status</h2>
+<div id="statusField">NO-STATUS</div>
+</body>
+</html>
diff --git a/native_client_sdk/src/examples/hello_world/helper_functions.cc b/native_client_sdk/src/examples/hello_world/helper_functions.cc
new file mode 100644
index 0000000..5ed26b5
--- /dev/null
+++ b/native_client_sdk/src/examples/hello_world/helper_functions.cc
@@ -0,0 +1,22 @@
+// Copyright (c) 2011 The Native Client 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 "examples/hello_world/helper_functions.h"
+
+#include <algorithm>
+
+namespace hello_world {
+
+int32_t FortyTwo() {
+ return 42;
+}
+
+std::string ReverseText(const std::string& text) {
+ std::string reversed_string(text);
+ // Use reverse to reverse |reversed_string| in place.
+ std::reverse(reversed_string.begin(), reversed_string.end());
+ return reversed_string;
+}
+} // namespace hello_world
+
diff --git a/native_client_sdk/src/examples/hello_world/helper_functions.h b/native_client_sdk/src/examples/hello_world/helper_functions.h
new file mode 100644
index 0000000..69ab874
--- /dev/null
+++ b/native_client_sdk/src/examples/hello_world/helper_functions.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2011 The Native Client Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXAMPLES_HELLO_WORLD_HELPER_FUNCTIONS_H_
+#define EXAMPLES_HELLO_WORLD_HELPER_FUNCTIONS_H_
+
+/// @file
+/// These functions are stand-ins for your complicated computations which you
+/// want to run in native code. We do two very simple things: return 42, and
+/// reverse a string. But you can imagine putting more complicated things here
+/// which might be difficult or slow to achieve in JavaScript, such as
+/// cryptography, artificial intelligence, signal processing, physics modeling,
+/// etc. See hello_world.cc for the code which is required for loading a NaCl
+/// application and exposing methods to JavaScript.
+
+#include <ppapi/c/pp_stdint.h>
+#include <string>
+
+namespace hello_world {
+
+/// This is the module's function that does the work to compute the value 42.
+int32_t FortyTwo();
+
+/// This function is passed a string and returns a copy of the string with the
+/// characters in reverse order.
+/// @param[in] text The string to reverse.
+/// @return A copy of @a text with the characters in reverse order.
+std::string ReverseText(const std::string& text);
+
+} // namespace hello_world
+
+#endif // EXAMPLES_HELLO_WORLD_HELPER_FUNCTIONS_H_
+
diff --git a/native_client_sdk/src/examples/hello_world/test_helper_functions.cc b/native_client_sdk/src/examples/hello_world/test_helper_functions.cc
new file mode 100644
index 0000000..f73f71a
--- /dev/null
+++ b/native_client_sdk/src/examples/hello_world/test_helper_functions.cc
@@ -0,0 +1,51 @@
+// Copyright (c) 2011 The Native Client Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This is an example of a simple unit test to verify that the logic helper
+// functions works as expected. Note that this looks like a 'normal' C++
+// program, with a main function. It is compiled and linked using the NaCl
+// toolchain, so in order to run it, you must use 'sel_ldr_x86_32' or
+// 'sel_ldr_x86_64' from the toolchain's bin directory.
+//
+// For example (assuming the toolchain bin directory is in your path):
+// sel_ldr_x86_32 test_helper_functions_x86_32_dbg.nexe
+//
+// You can also use the 'test32', or 'test64' SCons target to run these tests.
+// For example, this will run the test in 32-bit mode on Mac or Linux:
+// ../scons test32
+// On Windows 64:
+// ..\scons test64
+
+#include "examples/hello_world/helper_functions.h"
+
+#include <cassert>
+#include <cstdio>
+#include <string>
+
+// A very simple macro to print 'passed' if boolean_expression is true and
+// 'FAILED' otherwise.
+// This is meant to approximate the functionality you would get from a real test
+// framework. You should feel free to build and use the test framework of your
+// choice.
+#define EXPECT_EQUAL(left, right)\
+printf("Check: \"" #left "\" == \"" #right "\" %s\n", \
+ ((left) == (right)) ? "passed" : "FAILED")
+
+using hello_world::FortyTwo;
+using hello_world::ReverseText;
+
+int main() {
+ EXPECT_EQUAL(FortyTwo(), 42);
+
+ std::string empty_string;
+ EXPECT_EQUAL(ReverseText(empty_string), empty_string);
+
+ std::string palindrome("able was i ere i saw elba");
+ EXPECT_EQUAL(ReverseText(palindrome), palindrome);
+
+ std::string alphabet("abcdefghijklmnopqrstuvwxyz");
+ std::string alphabet_backwards("zyxwvutsrqponmlkjihgfedcba");
+ EXPECT_EQUAL(ReverseText(alphabet), alphabet_backwards);
+}
+
diff --git a/native_client_sdk/src/examples/hello_world_c/build.scons b/native_client_sdk/src/examples/hello_world_c/build.scons
new file mode 100644
index 0000000..90666e5
--- /dev/null
+++ b/native_client_sdk/src/examples/hello_world_c/build.scons
@@ -0,0 +1,40 @@
+#! -*- python -*-
+#
+# Copyright (c) 2011 The Native Client Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import make_nacl_env
+import os
+
+nacl_env = make_nacl_env.NaClEnvironment(
+ nacl_platform=os.getenv('NACL_TARGET_PLATFORM'),
+ install_subdir='hello_world_c', lib_prefix='..')
+nacl_env.Append(
+ # Add a CPPPATH that enables the full-path #include directives, such as
+ # #include "examples/sine_synth/sine_synth.h"
+ CPPPATH=[os.path.dirname(os.path.dirname(os.getcwd()))],
+ # Strict ANSI compliance.
+ EXTRA_CCFLAGS=['-pedantic'],
+ )
+
+sources = ['hello_world_c.c']
+
+opt_nexes, dbg_nexes = nacl_env.AllNaClModules(sources, 'hello_world_c')
+
+# This target is used by the SDK build system to provide a prebuilt version
+# of the example in the SDK installer.
+nacl_env.InstallPrebuilt('hello_world_c')
+
+app_files = [
+ 'hello_world_c.html',
+ 'hello_world_c.nmf',
+ ]
+
+# Split the install of the .nexes from the other app sources so that the strip
+# action is applied to the .nexes only.
+install_nexes = nacl_env.NaClStrippedInstall(dir=nacl_env['NACL_INSTALL_ROOT'],
+ source=opt_nexes)
+install_app = nacl_env.Install(dir=nacl_env['NACL_INSTALL_ROOT'],
+ source=app_files)
+nacl_env.Alias('install', source=install_app + install_nexes)
diff --git a/native_client_sdk/src/examples/hello_world_c/hello_world_c.c b/native_client_sdk/src/examples/hello_world_c/hello_world_c.c
new file mode 100644
index 0000000..939e724
--- /dev/null
+++ b/native_client_sdk/src/examples/hello_world_c/hello_world_c.c
@@ -0,0 +1,287 @@
+/* Copyright (c) 2011 The Native Client Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/** @file hello_world.c
+ * This example demonstrates loading, running and scripting a very simple
+ * NaCl module.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "ppapi/c/pp_errors.h"
+#include "ppapi/c/pp_module.h"
+#include "ppapi/c/pp_var.h"
+#include "ppapi/c/ppb.h"
+#include "ppapi/c/ppb_instance.h"
+#include "ppapi/c/ppb_messaging.h"
+#include "ppapi/c/ppb_var.h"
+#include "ppapi/c/ppp.h"
+#include "ppapi/c/ppp_instance.h"
+#include "ppapi/c/ppp_messaging.h"
+
+struct MessageInfo {
+ PP_Instance instance;
+ struct PP_Var message;
+};
+
+static const char* const kReverseTextMethodId = "reverseText";
+static const char* const kFortyTwoMethodId = "fortyTwo";
+static const char kMessageArgumentSeparator = ':';
+static const char kNullTerminator = '\0';
+
+static struct PPB_Messaging* ppb_messaging_interface = NULL;
+static struct PPB_Var* ppb_var_interface = NULL;
+static PP_Module module_id = 0;
+
+
+/**
+ * Returns a mutable C string contained in the @a var or NULL if @a var is not
+ * string. This makes a copy of the string in the @ var and adds a NULL
+ * terminator. Note that VarToUtf8() does not guarantee the NULL terminator on
+ * the returned string. See the comments for VatToUtf8() in ppapi/c/ppb_var.h
+ * for more info. The caller is responsible for freeing the returned memory.
+ * @param[in] var PP_Var containing string.
+ * @return a C string representation of @a var.
+ * @note The caller is responsible for freeing the returned string.
+ */
+static char* VarToCStr(struct PP_Var var) {
+ uint32_t len = 0;
+ if (ppb_var_interface != NULL) {
+ const char* var_c_str = ppb_var_interface->VarToUtf8(var, &len);
+ if (len > 0) {
+ char* c_str = (char*)malloc(len + 1);
+ memcpy(c_str, var_c_str, len);
+ c_str[len] = kNullTerminator;
+ return c_str;
+ }
+ }
+ return NULL;
+}
+
+/**
+ * Creates new string PP_Var from C string. The resulting object will be a
+ * refcounted string object. It will be AddRef()ed for the caller. When the
+ * caller is done with it, it should be Release()d.
+ * @param[in] str C string to be converted to PP_Var
+ * @return PP_Var containing string.
+ */
+static struct PP_Var CStrToVar(const char* str) {
+ if (ppb_var_interface != NULL) {
+ return ppb_var_interface->VarFromUtf8(module_id, str, strlen(str));
+ }
+ return PP_MakeUndefined();
+}
+
+/**
+ * Reverse C string in-place.
+ * @param[in,out] str C string to be reversed
+ */
+static void ReverseStr(char* str) {
+ char* right = str + strlen(str) - 1;
+ char* left = str;
+ while (left < right) {
+ char tmp = *left;
+ *left++ = *right;
+ *right-- = tmp;
+ }
+}
+
+/**
+ * A simple function that always returns 42.
+ * @return always returns the integer 42
+ */
+static struct PP_Var FortyTwo() {
+ return PP_MakeInt32(42);
+}
+
+/**
+ * Called when the NaCl module is instantiated on the web page. The identifier
+ * of the new instance will be passed in as the first argument (this value is
+ * generated by the browser and is an opaque handle). This is called for each
+ * instantiation of the NaCl module, which is each time the <embed> tag for
+ * this module is encountered.
+ *
+ * If this function reports a failure (by returning @a PP_FALSE), the NaCl
+ * module will be deleted and DidDestroy will be called.
+ * @param[in] instance The identifier of the new instance representing this
+ * NaCl module.
+ * @param[in] argc The number of arguments contained in @a argn and @a argv.
+ * @param[in] argn An array of argument names. These argument names are
+ * supplied in the <embed> tag, for example:
+ * <embed id="nacl_module" dimensions="2">
+ * will produce two arguments, one named "id" and one named "dimensions".
+ * @param[in] argv An array of argument values. These are the values of the
+ * arguments listed in the <embed> tag. In the above example, there will
+ * be two elements in this array, "nacl_module" and "2". The indices of
+ * these values match the indices of the corresponding names in @a argn.
+ * @return @a PP_TRUE on success.
+ */
+static PP_Bool Instance_DidCreate(PP_Instance instance,
+ uint32_t argc,
+ const char* argn[],
+ const char* argv[]) {
+ return PP_TRUE;
+}
+
+/**
+ * Called when the NaCl module is destroyed. This will always be called,
+ * even if DidCreate returned failure. This routine should deallocate any data
+ * associated with the instance.
+ * @param[in] instance The identifier of the instance representing this NaCl
+ * module.
+ */
+static void Instance_DidDestroy(PP_Instance instance) {
+}
+
+/**
+ * Called when the position, the size, or the clip rect of the element in the
+ * browser that corresponds to this NaCl module has changed.
+ * @param[in] instance The identifier of the instance representing this NaCl
+ * module.
+ * @param[in] position The location on the page of this NaCl module. This is
+ * relative to the top left corner of the viewport, which changes as the
+ * page is scrolled.
+ * @param[in] clip The visible region of the NaCl module. This is relative to
+ * the top left of the plugin's coordinate system (not the page). If the
+ * plugin is invisible, @a clip will be (0, 0, 0, 0).
+ */
+static void Instance_DidChangeView(PP_Instance instance,
+ const struct PP_Rect* position,
+ const struct PP_Rect* clip) {
+}
+
+/**
+ * Notification that the given NaCl module has gained or lost focus.
+ * Having focus means that keyboard events will be sent to the NaCl module
+ * represented by @a instance. A NaCl module's default condition is that it
+ * will not have focus.
+ *
+ * Note: clicks on NaCl modules will give focus only if you handle the
+ * click event. You signal if you handled it by returning @a true from
+ * HandleInputEvent. Otherwise the browser will bubble the event and give
+ * focus to the element on the page that actually did end up consuming it.
+ * If you're not getting focus, check to make sure you're returning true from
+ * the mouse click in HandleInputEvent.
+ * @param[in] instance The identifier of the instance representing this NaCl
+ * module.
+ * @param[in] has_focus Indicates whether this NaCl module gained or lost
+ * event focus.
+ */
+static void Instance_DidChangeFocus(PP_Instance instance,
+ PP_Bool has_focus) {
+}
+
+/**
+ * Handler that gets called after a full-frame module is instantiated based on
+ * registered MIME types. This function is not called on NaCl modules. This
+ * function is essentially a place-holder for the required function pointer in
+ * the PPP_Instance structure.
+ * @param[in] instance The identifier of the instance representing this NaCl
+ * module.
+ * @param[in] url_loader A PP_Resource an open PPB_URLLoader instance.
+ * @return PP_FALSE.
+ */
+static PP_Bool Instance_HandleDocumentLoad(PP_Instance instance,
+ PP_Resource url_loader) {
+ /* NaCl modules do not need to handle the document load function. */
+ return PP_FALSE;
+}
+
+/**
+ * Handler for messages coming in from the browser via postMessage. Extracts
+ * the method call from @a message, parses it for method name and value, then
+ * calls the appropriate function. In the case of the reverseString method, the
+ * message format is a simple colon-separated string. The first part of the
+ * string up to the colon is the method name; after that is the string argument.
+ * @param[in] instance The instance ID.
+ * @param[in] message The contents, copied by value, of the message sent from
+ * browser via postMessage.
+ */
+void Messaging_HandleMessage(PP_Instance instance, struct PP_Var var_message) {
+ if (var_message.type != PP_VARTYPE_STRING) {
+ /* Only handle string messages */
+ return;
+ }
+ char* message = VarToCStr(var_message);
+ if (message == NULL)
+ return;
+ struct PP_Var var_result = PP_MakeUndefined();
+ if (strncmp(message, kFortyTwoMethodId, strlen(kFortyTwoMethodId)) == 0) {
+ var_result = FortyTwo();
+ } else if (strncmp(message,
+ kReverseTextMethodId,
+ strlen(kReverseTextMethodId)) == 0) {
+ /* Use everything after the ':' in |message| as the string argument. */
+ char* string_arg = strchr(message, kMessageArgumentSeparator);
+ if (string_arg != NULL) {
+ string_arg += 1; /* Advance past the ':' separator. */
+ ReverseStr(string_arg);
+ var_result = CStrToVar(string_arg);
+ }
+ }
+ free(message);
+
+ /* Echo the return result back to browser. Note that HandleMessage is always
+ * called on the main thread, so it's OK to post the message back to the
+ * browser directly from here. This return post is asynchronous.
+ */
+ ppb_messaging_interface->PostMessage(instance, var_result);
+ /* If the message was created using VarFromUtf8() it needs to be released.
+ * See the comments about VarFromUtf8() in ppapi/c/ppb_var.h for more
+ * information.
+ */
+ if (var_result.type == PP_VARTYPE_STRING) {
+ ppb_var_interface->Release(var_result);
+ }
+}
+
+/**
+ * Entry points for the module.
+ * Initialize needed interfaces: PPB_Core, PPB_Messaging and PPB_Var.
+ * @param[in] a_module_id module ID
+ * @param[in] get_browser pointer to PPB_GetInterface
+ * @return PP_OK on success, any other value on failure.
+ */
+PP_EXPORT int32_t PPP_InitializeModule(PP_Module a_module_id,
+ PPB_GetInterface get_browser) {
+ module_id = a_module_id;
+ ppb_messaging_interface =
+ (struct PPB_Messaging*)(get_browser(PPB_MESSAGING_INTERFACE));
+ ppb_var_interface = (struct PPB_Var*)(get_browser(PPB_VAR_INTERFACE));
+
+ return PP_OK;
+}
+
+/**
+ * Returns an interface pointer for the interface of the given name, or NULL
+ * if the interface is not supported.
+ * @param[in] interface_name name of the interface
+ * @return pointer to the interface
+ */
+PP_EXPORT const void* PPP_GetInterface(const char* interface_name) {
+ if (strcmp(interface_name, PPP_INSTANCE_INTERFACE) == 0) {
+ static struct PPP_Instance instance_interface = {
+ &Instance_DidCreate,
+ &Instance_DidDestroy,
+ &Instance_DidChangeView,
+ &Instance_DidChangeFocus,
+ &Instance_HandleDocumentLoad,
+ };
+ return &instance_interface;
+ } else if (strcmp(interface_name, PPP_MESSAGING_INTERFACE) == 0) {
+ static struct PPP_Messaging messaging_interface = {
+ &Messaging_HandleMessage
+ };
+ return &messaging_interface;
+ }
+ return NULL;
+}
+
+/**
+ * Called before the plugin module is unloaded.
+ */
+PP_EXPORT void PPP_ShutdownModule() {
+}
diff --git a/native_client_sdk/src/examples/hello_world_c/hello_world_c.html b/native_client_sdk/src/examples/hello_world_c/hello_world_c.html
new file mode 100644
index 0000000..e5511c6
--- /dev/null
+++ b/native_client_sdk/src/examples/hello_world_c/hello_world_c.html
@@ -0,0 +1,126 @@
+<!DOCTYPE html>
+<html>
+ <!--
+ Copyright (c) 2011 The Native Client Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style license that can be
+ found in the LICENSE file.
+ -->
+<head>
+ <title>Hello, World!</title>
+
+ <script type="text/javascript">
+ helloWorldModule = null; // Global application object.
+ statusText = 'NO-STATUS';
+
+ // Indicate success when the NaCl module has loaded.
+ function moduleDidLoad() {
+ helloWorldModule = document.getElementById('hello_world');
+ updateStatus('SUCCESS');
+ }
+
+ // Handle a message coming from the NaCl module.
+ function handleMessage(message_event) {
+ alert(message_event.data);
+ }
+
+ // If the page loads before the Native Client module loads, then set the
+ // status message indicating that the module is still loading. Otherwise,
+ // do not change the status message.
+ function pageDidLoad() {
+ // Set the focus on the text input box. Doing this means you can press
+ // return as soon as the page loads, and it will fire the reversetText()
+ // function.
+ document.forms.helloForm.inputBox.focus();
+ if (helloWorldModule == null) {
+ updateStatus('LOADING...');
+ } else {
+ // It's possible that the Native Client module onload event fired
+ // before the page's onload event. In this case, the status message
+ // will reflect 'SUCCESS', but won't be displayed. This call will
+ // display the current message.
+ updateStatus();
+ }
+ }
+
+ function fortyTwo() {
+ helloWorldModule.postMessage('fortyTwo');
+ }
+
+ function reverseText() {
+ // Grab the text from the text box, pass it into reverseText()
+ var inputBox = document.forms.helloForm.inputBox;
+ helloWorldModule.postMessage('reverseText:' + inputBox.value);
+ // Note: a |false| return tells the <form> tag to cancel the GET action
+ // when submitting the form.
+ return false;
+ }
+
+ // Set the global status message. If the element with id 'statusField'
+ // exists, then set its HTML to the status message as well.
+ // opt_message The message test. If this is null or undefined, then
+ // attempt to set the element with id 'statusField' to the value of
+ // |statusText|.
+ function updateStatus(opt_message) {
+ if (opt_message)
+ statusText = opt_message;
+ var statusField = document.getElementById('statusField');
+ if (statusField) {
+ statusField.innerHTML = statusText;
+ }
+ }
+ </script>
+</head>
+<body onload="pageDidLoad()">
+
+<h1>Native Client Simple Module</h1>
+<p>
+ <form name="helloForm"
+ action=""
+ method="get"
+ onsubmit="return reverseText()">
+ <input type="text" id="inputBox" name="inputBox" value="Hello world" /><p/>
+ <input type="button" value="Call fortyTwo()" onclick="fortyTwo()" />
+ <input type="submit" value="Call reverseText()" />
+ </form>
+ <!-- Load the published .nexe. This includes the 'src' attribute which
+ shows how to load multi-architecture modules. Each entry in the "nexes"
+ object in the .nmf manifest file is a key-value pair: the key is the runtime
+ ('x86-32', 'x86-64', etc.); the value is a URL for the desired NaCl module.
+ To load the debug versions of your .nexes, set the 'src' attribute to the
+ _dbg.nmf version of the manifest file.
+
+ Note: The <EMBED> element is wrapped inside a <DIV>, which has both a 'load'
+ and a 'message' event listener attached. This wrapping method is used
+ instead of attaching the event listeners directly to the <EMBED> element to
+ ensure that the listeners are active before the NaCl module 'load' event
+ fires. This also allows you to use PPB_Messaging.PostMessage() (in C) or
+ pp::Instance.PostMessage() (in C++) from within the initialization code in
+ your NaCl module.
+ -->
+ <div id="listener">
+ <script type="text/javascript">
+ var listener = document.getElementById('listener')
+ listener.addEventListener('load', moduleDidLoad, true);
+ listener.addEventListener('message', handleMessage, true);
+ </script>
+
+ <embed name="nacl_module"
+ id="hello_world"
+ width=0 height=0
+ src="hello_world_c.nmf"
+ type="application/x-nacl" />
+ </div>
+
+</p>
+
+<p>If the module is working correctly, a click on the "Call fortyTwo()" button
+ should open a popup dialog containing <b>42</b> as its value.</p>
+
+<p> Clicking on the "Call reverseText()" button
+ should open a popup dialog containing the textbox contents and its reverse
+ as its value.</p>
+
+<h2>Status</h2>
+<div id="statusField">NO-STATUS</div>
+</body>
+</html>
diff --git a/native_client_sdk/src/examples/hello_world_c/hello_world_c_dbg.html b/native_client_sdk/src/examples/hello_world_c/hello_world_c_dbg.html
new file mode 100644
index 0000000..c93fb79
--- /dev/null
+++ b/native_client_sdk/src/examples/hello_world_c/hello_world_c_dbg.html
@@ -0,0 +1,126 @@
+<!DOCTYPE html>
+<html>
+ <!--
+ Copyright (c) 2011 The Native Client Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style license that can be
+ found in the LICENSE file.
+ -->
+<head>
+ <title>Hello, World!</title>
+
+ <script type="text/javascript">
+ helloWorldModule = null; // Global application object.
+ statusText = 'NO-STATUS';
+
+ // Indicate success when the NaCl module has loaded.
+ function moduleDidLoad() {
+ helloWorldModule = document.getElementById('hello_world');
+ updateStatus('SUCCESS');
+ }
+
+ // Handle a message coming from the NaCl module.
+ function handleMessage(message_event) {
+ alert(message_event.data);
+ }
+
+ // If the page loads before the Native Client module loads, then set the
+ // status message indicating that the module is still loading. Otherwise,
+ // do not change the status message.
+ function pageDidLoad() {
+ // Set the focus on the text input box. Doing this means you can press
+ // return as soon as the page loads, and it will fire the reversetText()
+ // function.
+ document.forms.helloForm.inputBox.focus();
+ if (helloWorldModule == null) {
+ updateStatus('LOADING...');
+ } else {
+ // It's possible that the Native Client module onload event fired
+ // before the page's onload event. In this case, the status message
+ // will reflect 'SUCCESS', but won't be displayed. This call will
+ // display the current message.
+ updateStatus();
+ }
+ }
+
+ function fortyTwo() {
+ helloWorldModule.postMessage('fortyTwo');
+ }
+
+ function reverseText() {
+ // Grab the text from the text box, pass it into reverseText()
+ var inputBox = document.forms.helloForm.inputBox;
+ helloWorldModule.postMessage('reverseText:' + inputBox.value);
+ // Note: a |false| return tells the <form> tag to cancel the GET action
+ // when submitting the form.
+ return false;
+ }
+
+ // Set the global status message. If the element with id 'statusField'
+ // exists, then set its HTML to the status message as well.
+ // opt_message The message test. If this is null or undefined, then
+ // attempt to set the element with id 'statusField' to the value of
+ // |statusText|.
+ function updateStatus(opt_message) {
+ if (opt_message)
+ statusText = opt_message;
+ var statusField = document.getElementById('statusField');
+ if (statusField) {
+ statusField.innerHTML = statusText;
+ }
+ }
+ </script>
+</head>
+<body onload="pageDidLoad()">
+
+<h1>Native Client Simple Module</h1>
+<p>
+ <form name="helloForm"
+ action=""
+ method="get"
+ onsubmit="return reverseText()">
+ <input type="text" id="inputBox" name="inputBox" value="Hello world" /><p>
+ <input type="button" value="Call fortyTwo()" onclick="fortyTwo()" />
+ <input type="submit" value="Call reverseText()" />
+ </form>
+ <!-- Load the published .nexe. This includes the 'src' attribute which
+ shows how to load multi-architecture modules. Each entry in the "nexes"
+ object in the .nmf manifest file is a key-value pair: the key is the runtime
+ ('x86-32', 'x86-64', etc.); the value is a URL for the desired NaCl module.
+ To load the debug versions of your .nexes, set the 'src' attribute to the
+ _dbg.nmf version of the manifest file.
+
+ Note: The <EMBED> element is wrapped inside a <DIV>, which has both a 'load'
+ and a 'message' event listener attached. This wrapping method is used
+ instead of attaching the event listeners directly to the <EMBED> element to
+ ensure that the listeners are active before the NaCl module 'load' event
+ fires. This also allows you to use PPB_Messaging.PostMessage() (in C) or
+ pp::Instance.PostMessage() (in C++) from within the initialization code in
+ your NaCl module.
+ -->
+ <div id="listener">
+ <script type="text/javascript">
+ var listener = document.getElementById('listener')
+ listener.addEventListener('load', moduleDidLoad, true);
+ listener.addEventListener('message', handleMessage, true);
+ </script>
+
+ <embed name="nacl_module"
+ id="hello_world"
+ width=0 height=0
+ src="hello_world_c_dbg.nmf"
+ type="application/x-nacl" />
+ </div>
+
+</p>
+
+<p>If the module is working correctly, a click on the "Call fortyTwo()" button
+ should open a popup dialog containing <b>42</b> as its value.</p>
+
+<p> Clicking on the "Call reverseText()" button
+ should open a popup dialog containing the textbox contents and its reverse
+ as its value.</p>
+
+<h2>Status</h2>
+<div id="statusField">NO-STATUS</div>
+</body>
+</html>
diff --git a/native_client_sdk/src/examples/httpd.cmd b/native_client_sdk/src/examples/httpd.cmd
new file mode 100644
index 0000000..7c64624
--- /dev/null
+++ b/native_client_sdk/src/examples/httpd.cmd
@@ -0,0 +1,8 @@
+@echo off
+setlocal
+
+PATH=%CYGWIN%;%PATH%
+REM Use the path to this file (httpd.cmd) to get the
+REM path to httpd.py, so that we can run httpd.cmd from
+REM any directory. Pass up to 9 arguments to httpd.py.
+python %~dp0\httpd.py %1 %2 %3 %4 %5 %6 %7 %8 %9
diff --git a/native_client_sdk/src/examples/httpd.py b/native_client_sdk/src/examples/httpd.py
new file mode 100755
index 0000000..3fa8b22
--- /dev/null
+++ b/native_client_sdk/src/examples/httpd.py
@@ -0,0 +1,125 @@
+#!/usr/bin/python
+#
+# Copyright (c) 2011 The Native Client Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+#
+
+"""A tiny web server.
+
+This is intended to be used for testing, and only run from within the examples
+directory.
+"""
+
+import BaseHTTPServer
+import logging
+import optparse
+import os
+import SimpleHTTPServer
+import SocketServer
+import sys
+import urlparse
+
+logging.getLogger().setLevel(logging.INFO)
+
+# Using 'localhost' means that we only accept connections
+# via the loop back interface.
+SERVER_PORT = 5103
+SERVER_HOST = ''
+
+# We only run from the examples or staging directory so
+# that not too much is exposed via this HTTP server. Everything in the
+# directory is served, so there should never be anything potentially sensitive
+# in the serving directory, especially if the machine might be a
+# multi-user machine and not all users are trusted. We only serve via
+# the loopback interface.
+
+SAFE_DIR_COMPONENTS = ['staging', 'examples']
+
+def SanityCheckDirectory():
+ if os.path.basename(os.getcwd()) in SAFE_DIR_COMPONENTS:
+ return
+ logging.error('For security, httpd.py should only be run from one of the')
+ logging.error('following directories: %s' % SAFE_DIR_COMPONENTS)
+ logging.error('We are currently in %s', os.getcwd())
+ sys.exit(1)
+
+
+# An HTTP server that will quit when |is_running| is set to False. We also use
+# SocketServer.ThreadingMixIn in order to handle requests asynchronously for
+# faster responses.
+class QuittableHTTPServer(SocketServer.ThreadingMixIn,
+ BaseHTTPServer.HTTPServer):
+ def serve_forever(self, timeout=0.5):
+ self.is_running = True
+ self.timeout = timeout
+ while self.is_running:
+ self.handle_request()
+
+ def shutdown(self):
+ self.is_running = False
+ return 1
+
+
+# "Safely" split a string at |sep| into a [key, value] pair. If |sep| does not
+# exist in |str|, then the entire |str| is the key and the value is set to an
+# empty string.
+def KeyValuePair(str, sep='='):
+ if sep in str:
+ return str.split(sep)
+ else:
+ return [str, '']
+
+
+# A small handler that looks for '?quit=1' query in the path and shuts itself
+# down if it finds that parameter.
+class QuittableHTTPHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
+ def do_GET(self):
+ (_, _, _, query, _) = urlparse.urlsplit(self.path)
+ url_params = dict([KeyValuePair(key_value)
+ for key_value in query.split('&')])
+ if 'quit' in url_params and '1' in url_params['quit']:
+ self.send_response(200, 'OK')
+ self.send_header('Content-type', 'text/html')
+ self.send_header('Content-length', '0')
+ self.end_headers()
+ self.server.shutdown()
+ return
+
+ SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
+
+
+def Run(server_address,
+ server_class=QuittableHTTPServer,
+ handler_class=QuittableHTTPHandler):
+ httpd = server_class(server_address, handler_class)
+ logging.info("Starting local server on port %d", server_address[1])
+ logging.info("To shut down send http://localhost:%d?quit=1",
+ server_address[1])
+ try:
+ httpd.serve_forever()
+ except KeyboardInterrupt:
+ logging.info("Received keyboard interrupt.")
+ httpd.server_close()
+
+ logging.info("Shutting down local server on port %d", server_address[1])
+
+
+if __name__ == '__main__':
+ usage_str = "usage: %prog [options] [optional_portnum]"
+ parser = optparse.OptionParser(usage=usage_str)
+ parser.add_option(
+ '--no_dir_check', dest='do_safe_check',
+ action='store_false', default=True,
+ help='Do not ensure that httpd.py is being run from a safe directory.')
+ (options, args) = parser.parse_args(sys.argv)
+ if options.do_safe_check:
+ SanityCheckDirectory()
+ if len(args) > 2:
+ print 'Too many arguments specified.'
+ parser.print_help()
+ elif len(args) == 2:
+ Run((SERVER_HOST, int(args[1])))
+ else:
+ Run((SERVER_HOST, SERVER_PORT))
+ sys.exit(0)
diff --git a/native_client_sdk/src/examples/index.html b/native_client_sdk/src/examples/index.html
new file mode 100644
index 0000000..3d72c28
--- /dev/null
+++ b/native_client_sdk/src/examples/index.html
@@ -0,0 +1,51 @@
+<!--
+ Copyright (c) 2011 The Native Client Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style license that can be
+ found in the LICENSE file.
+-->
+
+<!DOCTYPE html PUBLIC
+ "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<style type="text/css">
+dt {
+ font-weight: bold;
+}
+dd {
+ margin-bottom: 12pt;
+ width: 600px;
+}
+</style>
+<link href="http://code.google.com/css/codesite.css" rel="stylesheet"
+ type="text/css" />
+<title>Native Client Examples</title>
+</head>
+<body>
+<h2>Native Client Examples</h2>
+<p>The examples are no longer pre-built in the SDK. To try out the Native
+Client examples right now in your Chrome web browser, please see the
+<a href="http://www.gonacl.com/dev/sdk.html">SDK page on GoNaCl.com</a> and
+download the SDK examples from the
+<a href="https://chrome.google.com/webstore/">Chrome Web Store</a>.</p>
+<p>If you would like to build and run the examples within the SDK
+then run these commands, starting from the examples directory:</p><br />
+<strong>Windows</strong>
+<blockquote><code>
+cd %NACL_SDK_ROOT%\%NACL_TARGET_PLATFORM%\examples<br />
+scons<br />
+cd %NACL_SDK_ROOT%\staging<br />
+httpd<br />
+</code></blockquote>
+<strong>Mac/Linux</strong>
+<blockquote><code>
+cd $NACL_SDK_ROOT/$NACL_TARGET_PLATFORM/examples<br />
+./scons<br />
+cd $NACL_SDK_ROOT/staging<br />
+./httpd.py<br />
+</code></blockquote>
+<p>Happy hacking!</p>
+</body>
+</html>
diff --git a/native_client_sdk/src/examples/index_staging.html b/native_client_sdk/src/examples/index_staging.html
new file mode 100644
index 0000000..c90ec1a
--- /dev/null
+++ b/native_client_sdk/src/examples/index_staging.html
@@ -0,0 +1,113 @@
+<!--
+ Copyright (c) 2011 The Native Client Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style license that can be
+ found in the LICENSE file.
+-->
+
+<!DOCTYPE html PUBLIC
+ "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<style type="text/css">
+dt {
+ font-weight: bold;
+}
+dd {
+ margin-bottom: 12pt;
+ width: 600px;
+}
+</style>
+<link href="http://code.google.com/css/codesite.css" rel="stylesheet"
+ type="text/css" />
+<title>Native Client Examples</title>
+</head>
+<body>
+<h2>Native Client Examples</h2>
+<p>This page lists all of the examples available in the most recent Native Client SDK bundle. Each example is designed to teach a few specific Native Client programming concepts.</p>
+<dl>
+ <dt><a href="hello_world_c/hello_world_c.html">Hello World in C</a></dt>
+ <dd>The Hello World In C example demonstrates the basic structure of all Native Client applications. This example loads a Native Client module and responds to button click events by showing alert panels.
+
+ <p>Teaching focus: Basic HTML, JavaScript, and module architecture; Messaging API.</p>
+ </dd>
+ <dt><a href="hello_world/hello_world.html">Hello World in C++</a></dt>
+ <dd>The Hello World C++ example demonstrates the basic structure of all Native Client applications. This example loads a Native Client module and responds to button click events by showing alert panels.
+
+ <p>Teaching focus: Basic HTML, JavaScript, and module architecture; Messaging API.</p>
+ </dd>
+<dt><a href="load_progress/load_progress.html">Load Progress</a></dt>
+ <dd> The Load Progress example demonstrates how to listen for and handle events that occur while a
+ NaCl module loads. This example listens for different load event types and dispatches different events to their respective handler. This example also checks for valid browser
+ version and shows how to calculate and display loading progress.
+
+ <p>Teaching focus: Progress event handling.</p>
+ </dd>
+ <dt><a href="pi_generator/pi_generator.html">Pi Generator</a></dt>
+ <dd> The Pi Generator example demonstrates creating a helper thread that estimate pi using the Monte Carlo
+ method while randomly putting 1,000,000,000 points inside a 2D square that shares two
+ sides with a quarter circle.
+
+ <p>Teaching focus: Thread creation, 2D graphics, view change events.</p>
+ </dd>
+<dt><a href="input_events/input_events.html">Input Events</a></dt>
+ <dd> The Input Events example demonstrates how to handle events triggered by the user. This example allows a user
+ to interact with a square representing a module instance. Events are displayed on the screen as the user clicks, scrolls, types, inside or outside
+ of the square.
+
+ <p>Teaching focus: Keyboard and mouse input, view change, and focus events.</p>
+ </dd>
+<dt><a href="sine_synth/sine_synth.html">Sine Wave Synthesizer</a></dt>
+ <dd> The Sine Wave Synthesizer example demonstrates playing sound (a sine wave).
+
+ <p>Teaching focus: Audio.</p>
+ </dd>
+<dt><a href="pong/pong.html">Pong</a></dt>
+ <dd> The Pong example demonstrates how to create a basic 2D video game and how to store application
+ information in a local persistent file. This game uses up and
+ down arrow keyboard input events to move the paddle.
+
+ <p>Teaching focus: File I/O, 2D graphics, input events.</p>
+ </dd>
+ <dt><a href="geturl/geturl.html">Get URL</a></dt>
+ <dd> The Get URL example demonstrates fetching an URL and then displaying its contents.
+
+ <p>Teaching focus: URL loading.</p>
+ </dd>
+ <dt><a href="multithreaded_input_events/mt_input_events.html">Multi-threaded Input Events</a></dt>
+ <dd>The Multithreaded Input Events example combines HTML, Javascript,
+ and C++ (the C++ is compiled to create a .nexe file).
+ The C++ shows how to handle input events in a multi-threaded application.
+ The main thread converts input events to non-pepper events and puts them on
+ a queue. The worker thread pulls them off of the queue, converts them to a
+ string, and then uses CallOnMainThread so that PostMessage can be send the
+ result of the worker thread to the browser.
+ </dd>
+ <dt><a href="tumbler/tumbler.html">Tumbler</a></dt>
+ <dd> The Tumbler example demonstrates how to create a 3D cube that you can rotate with your mouse while pressing the
+ left mouse button. This example creates a 3D context and draws to it using
+ OpenGL ES. The JavaScript implements a virtual trackball interface to
+ map mouse movements into 3D rotations using simple 3D vector math and
+ quaternions.
+
+ <p>Teaching focus: 3D graphics</p>
+ </dd>
+ <dt><a href="fullscreen_tumbler/fullscreen_tumbler.html">Full-screen Tumbler</a></dt>
+ <dd> This is a modified version of the Tumbler example above that supports
+ full-screen display. It is in every way identical to Tumbler in
+ functionality, except that it adds the ability to switch to/from
+ full-screen display by pressing the Enter key.
+
+ <p>Teaching focus: Full-screen</p>
+ </dd>
+ <dt><a href="mouselock/mouselock.html">Mouse Locker</a></dt>
+ <dd> The Mouselock example demonstrates how to use the MouseLock API to hide
+ the mouse cursor. Mouse lock is only available in full-screen mode. You can
+ lock and unlock the mouse while in full-screen mode by pressing the Enter key.
+
+ <p>Teaching focus: Mouse lock, Full-screen</p>
+ </dd>
+</dl>
+</body>
+</html>
diff --git a/native_client_sdk/src/examples/input_events/build.scons b/native_client_sdk/src/examples/input_events/build.scons
new file mode 100644
index 0000000..3ccfc5b
--- /dev/null
+++ b/native_client_sdk/src/examples/input_events/build.scons
@@ -0,0 +1,41 @@
+#! -*- python -*-
+#
+# Copyright (c) 2011 The Native Client Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import make_nacl_env
+import nacl_utils
+import os
+
+nacl_env = make_nacl_env.NaClEnvironment(
+ use_c_plus_plus_libs=True, nacl_platform=os.getenv('NACL_TARGET_PLATFORM'),
+ install_subdir='input_events', lib_prefix='..')
+nacl_env.Append(
+ # Add a CPPPATH that enables the full-path #include directives, such as
+ # #include "examples/sine_synth/sine_synth.h"
+ CPPPATH=[os.path.dirname(os.path.dirname(os.getcwd()))],
+ # Strict ANSI compliance.
+ EXTRA_CCFLAGS=['-pedantic'],
+ )
+
+sources = ['input_events.cc']
+
+opt_nexes, dbg_nexes = nacl_env.AllNaClModules(sources, 'input_events')
+
+# This target is used by the SDK build system to provide a prebuilt version
+# of the example in the SDK installer.
+nacl_env.InstallPrebuilt('input_events')
+
+app_files = [
+ 'input_events.html',
+ 'input_events.nmf',
+ ]
+
+# Split the install of the .nexes from the other app sources so that the strip
+# action is applied to the .nexes only.
+install_nexes = nacl_env.NaClStrippedInstall(dir=nacl_env['NACL_INSTALL_ROOT'],
+ source=opt_nexes)
+install_app = nacl_env.Install(dir=nacl_env['NACL_INSTALL_ROOT'],
+ source=app_files)
+nacl_env.Alias('install', source=install_app + install_nexes)
diff --git a/native_client_sdk/src/examples/input_events/input_events.cc b/native_client_sdk/src/examples/input_events/input_events.cc
new file mode 100644
index 0000000..8ef2419
--- /dev/null
+++ b/native_client_sdk/src/examples/input_events/input_events.cc
@@ -0,0 +1,251 @@
+// Copyright (c) 2011 The Native Client Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// C headers
+#include <cassert>
+#include <cstdio>
+
+// C++ headers
+#include <sstream>
+#include <string>
+
+// NaCl
+#include "ppapi/cpp/input_event.h"
+#include "ppapi/cpp/instance.h"
+#include "ppapi/cpp/module.h"
+#include "ppapi/cpp/point.h"
+#include "ppapi/cpp/var.h"
+
+namespace {
+const char* const kDidChangeView = "DidChangeView";
+const char* const kHandleInputEvent = "DidHandleInputEvent";
+const char* const kDidChangeFocus = "DidChangeFocus";
+const char* const kHaveFocus = "HaveFocus";
+const char* const kDontHaveFocus = "DontHaveFocus";
+
+// Convert a given modifier to a descriptive string. Note that the actual
+// declared type of modifier in each of the event classes is uint32_t, but it is
+// expected to be interpreted as a bitfield of 'or'ed PP_InputEvent_Modifier
+// values.
+std::string ModifierToString(uint32_t modifier) {
+ std::string s;
+ if (modifier & PP_INPUTEVENT_MODIFIER_SHIFTKEY) {
+ s += "shift ";
+ }
+ if (modifier & PP_INPUTEVENT_MODIFIER_CONTROLKEY) {
+ s += "ctrl ";
+ }
+ if (modifier & PP_INPUTEVENT_MODIFIER_ALTKEY) {
+ s += "alt ";
+ }
+ if (modifier & PP_INPUTEVENT_MODIFIER_METAKEY) {
+ s += "meta ";
+ }
+ if (modifier & PP_INPUTEVENT_MODIFIER_ISKEYPAD) {
+ s += "keypad ";
+ }
+ if (modifier & PP_INPUTEVENT_MODIFIER_ISAUTOREPEAT) {
+ s += "autorepeat ";
+ }
+ if (modifier & PP_INPUTEVENT_MODIFIER_LEFTBUTTONDOWN) {
+ s += "left-button-down ";
+ }
+ if (modifier & PP_INPUTEVENT_MODIFIER_MIDDLEBUTTONDOWN) {
+ s += "middle-button-down ";
+ }
+ if (modifier & PP_INPUTEVENT_MODIFIER_RIGHTBUTTONDOWN) {
+ s += "right-button-down ";
+ }
+ if (modifier & PP_INPUTEVENT_MODIFIER_CAPSLOCKKEY) {
+ s += "caps-lock ";
+ }
+ if (modifier & PP_INPUTEVENT_MODIFIER_NUMLOCKKEY) {
+ s += "num-lock ";
+ }
+ return s;
+}
+
+std::string MouseButtonToString(PP_InputEvent_MouseButton button) {
+ switch (button) {
+ case PP_INPUTEVENT_MOUSEBUTTON_NONE:
+ return "None";
+ case PP_INPUTEVENT_MOUSEBUTTON_LEFT:
+ return "Left";
+ case PP_INPUTEVENT_MOUSEBUTTON_MIDDLE:
+ return "Middle";
+ case PP_INPUTEVENT_MOUSEBUTTON_RIGHT:
+ return "Right";
+ default:
+ std::ostringstream stream;
+ stream << "Unrecognized ("
+ << static_cast<int32_t>(button)
+ << ")";
+ return stream.str();
+ }
+}
+
+} // namespace
+
+class EventInstance : public pp::Instance {
+ public:
+ explicit EventInstance(PP_Instance instance)
+ : pp::Instance(instance) {
+ RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE | PP_INPUTEVENT_CLASS_WHEEL);
+ RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_KEYBOARD);
+ }
+ virtual ~EventInstance() {}
+
+ /// Clicking outside of the instance's bounding box
+ /// will create a DidChangeFocus event (the NaCl instance is
+ /// out of focus). Clicking back inside the instance's
+ /// bounding box will create another DidChangeFocus event
+ /// (the NaCl instance is back in focus). The default is
+ /// that the instance is out of focus.
+ void DidChangeFocus(bool focus) {
+ PostMessage(pp::Var(kDidChangeFocus));
+ if (focus == true) {
+ PostMessage(pp::Var(kHaveFocus));
+ } else {
+ PostMessage(pp::Var(kDontHaveFocus));
+ }
+ }
+
+ /// Scrolling the mouse wheel causes a DidChangeView event.
+ void DidChangeView(const pp::Rect& position,
+ const pp::Rect& clip) {
+ PostMessage(pp::Var(kDidChangeView));
+ }
+
+ void GotKeyEvent(const pp::KeyboardInputEvent& key_event,
+ const std::string& kind) {
+ std::ostringstream stream;
+ stream << pp_instance() << ":"
+ << " Key event:" << kind
+ << " modifier:" << ModifierToString(key_event.GetModifiers())
+ << " key_code:" << key_event.GetKeyCode()
+ << " time:" << key_event.GetTimeStamp()
+ << " text:" << key_event.GetCharacterText().DebugString()
+ << "\n";
+ PostMessage(stream.str());
+ }
+
+ void GotMouseEvent(const pp::MouseInputEvent& mouse_event,
+ const std::string& kind) {
+ std::ostringstream stream;
+ stream << pp_instance() << ":"
+ << " Mouse event:" << kind
+ << " modifier:" << ModifierToString(mouse_event.GetModifiers())
+ << " button:" << MouseButtonToString(mouse_event.GetButton())
+ << " x:" << mouse_event.GetPosition().x()
+ << " y:" << mouse_event.GetPosition().y()
+ << " click_count:" << mouse_event.GetClickCount()
+ << " time:" << mouse_event.GetTimeStamp()
+ << "\n";
+ PostMessage(stream.str());
+ }
+
+ void GotWheelEvent(const pp::WheelInputEvent& wheel_event) {
+ std::ostringstream stream;
+ stream << pp_instance() << ": Wheel event."
+ << " modifier:" << ModifierToString(wheel_event.GetModifiers())
+ << " deltax:" << wheel_event.GetDelta().x()
+ << " deltay:" << wheel_event.GetDelta().y()
+ << " wheel_ticks_x:" << wheel_event.GetTicks().x()
+ << " wheel_ticks_y:"<< wheel_event.GetTicks().y()
+ << " scroll_by_page: "
+ << (wheel_event.GetScrollByPage() ? "true" : "false")
+ << "\n";
+ PostMessage(stream.str());
+ }
+
+ // Handle an incoming input event by switching on type and dispatching
+ // to the appropriate subtype handler.
+ //
+ // HandleInputEvent operates on the main Pepper thread. In large
+ // real-world applications, you'll want to create a separate thread
+ // that puts events in a queue and handles them independant of the main
+ // thread so as not to slow down the browser. There is an additional
+ // version of this example in the examples directory that demonstrates
+ // this best practice.
+ virtual bool HandleInputEvent(const pp::InputEvent& event) {
+ PostMessage(pp::Var(kHandleInputEvent));
+ switch (event.GetType()) {
+ case PP_INPUTEVENT_TYPE_UNDEFINED:
+ break;
+ case PP_INPUTEVENT_TYPE_MOUSEDOWN:
+ GotMouseEvent(pp::MouseInputEvent(event), "Down");
+ break;
+ case PP_INPUTEVENT_TYPE_MOUSEUP:
+ GotMouseEvent(pp::MouseInputEvent(event), "Up");
+ break;
+ case PP_INPUTEVENT_TYPE_MOUSEMOVE:
+ GotMouseEvent(pp::MouseInputEvent(event), "Move");
+ break;
+ case PP_INPUTEVENT_TYPE_MOUSEENTER:
+ GotMouseEvent(pp::MouseInputEvent(event), "Enter");
+ break;
+ case PP_INPUTEVENT_TYPE_MOUSELEAVE:
+ GotMouseEvent(pp::MouseInputEvent(event), "Leave");
+ break;
+ case PP_INPUTEVENT_TYPE_WHEEL:
+ GotWheelEvent(pp::WheelInputEvent(event));
+ break;
+ case PP_INPUTEVENT_TYPE_RAWKEYDOWN:
+ GotKeyEvent(pp::KeyboardInputEvent(event), "RawKeyDown");
+ break;
+ case PP_INPUTEVENT_TYPE_KEYDOWN:
+ GotKeyEvent(pp::KeyboardInputEvent(event), "Down");
+ break;
+ case PP_INPUTEVENT_TYPE_KEYUP:
+ GotKeyEvent(pp::KeyboardInputEvent(event), "Up");
+ break;
+ case PP_INPUTEVENT_TYPE_CHAR:
+ GotKeyEvent(pp::KeyboardInputEvent(event), "Character");
+ break;
+ case PP_INPUTEVENT_TYPE_CONTEXTMENU:
+ GotKeyEvent(pp::KeyboardInputEvent(event), "Context");
+ break;
+ // Note that if we receive an IME event we just send a message back
+ // to the browser to indicate we have received it.
+ case PP_INPUTEVENT_TYPE_IME_COMPOSITION_START:
+ PostMessage(pp::Var("PP_INPUTEVENT_TYPE_IME_COMPOSITION_START"));
+ break;
+ case PP_INPUTEVENT_TYPE_IME_COMPOSITION_UPDATE:
+ PostMessage(pp::Var("PP_INPUTEVENT_TYPE_IME_COMPOSITION_UPDATE"));
+ break;
+ case PP_INPUTEVENT_TYPE_IME_COMPOSITION_END:
+ PostMessage(pp::Var("PP_INPUTEVENT_TYPE_IME_COMPOSITION_END"));
+ break;
+ case PP_INPUTEVENT_TYPE_IME_TEXT:
+ PostMessage(pp::Var("PP_INPUTEVENT_TYPE_IME_COMPOSITION_TEXT"));
+ break;
+ default:
+ assert(false);
+ return false;
+ }
+ return true;
+ }
+};
+
+// The EventModule provides an implementation of pp::Module that creates
+// EventInstance objects when invoked. This is part of the glue code that makes
+// our example accessible to ppapi.
+class EventModule : public pp::Module {
+ public:
+ EventModule() : pp::Module() {}
+ virtual ~EventModule() {}
+
+ virtual pp::Instance* CreateInstance(PP_Instance instance) {
+ return new EventInstance(instance);
+ }
+};
+
+// Implement the required pp::CreateModule function that creates our specific
+// kind of Module (in this case, EventModule). This is part of the glue code
+// that makes our example accessible to ppapi.
+namespace pp {
+ Module* CreateModule() {
+ return new EventModule();
+ }
+}
diff --git a/native_client_sdk/src/examples/input_events/input_events.html b/native_client_sdk/src/examples/input_events/input_events.html
new file mode 100644
index 0000000..3f1695d
--- /dev/null
+++ b/native_client_sdk/src/examples/input_events/input_events.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<html>
+ <!--
+ Copyright (c) 2011 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.
+ -->
+<head>
+ <title>Input Events</title>
+
+ <script type="text/javascript">
+ var kMaxArraySize = 20;
+ var messageArray = new Array();
+
+ function $(id) {
+ return document.getElementById(id);
+ }
+
+ function receiveMessage(message) {
+ // Show last |kMaxArraySize| events in html.
+ messageArray.push(message.data);
+ if (messageArray.length > kMaxArraySize) {
+ messageArray.shift();
+ }
+ var newData = messageArray.join('<BR>');
+ document.getElementById('eventString').innerHTML = newData;
+ // Print event to console.
+ console.log(message.data);
+ }
+ </script>
+</head>
+<body>
+<h1>InputEvent Handling Example</h1>
+ <div id="listener">
+ <script type="text/javascript">
+ $('listener').addEventListener('message', receiveMessage, true);
+ </script>
+
+ <embed name="nacl_module"
+ id="event_module"
+ width=400 height=400
+ src="input_events.nmf"
+ type="application/x-nacl"
+ style="background-color:gray" />
+ </div>
+<p>
+This example demonstrates handling of input events in PPAPI.</p>
+<p>
+Each time an input event happens in the context of the gray box,
+the embedded NaCl module posts a message describing the event
+back to JavaScript, which prints a message to the JavaScript
+console in Chrome and to a string on the page.</p>
+<h2>Events</h2>
+<pre>
+<p><b id='eventString'>None</b></p>
+</pre>
+</body>
+</html>
diff --git a/native_client_sdk/src/examples/load_progress/build.scons b/native_client_sdk/src/examples/load_progress/build.scons
new file mode 100644
index 0000000..1c8a6a6
--- /dev/null
+++ b/native_client_sdk/src/examples/load_progress/build.scons
@@ -0,0 +1,51 @@
+#! -*- python -*-
+#
+# Copyright (c) 2011 The Native Client Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import make_nacl_env
+import os
+
+nacl_env = make_nacl_env.NaClEnvironment(
+ use_c_plus_plus_libs=True, nacl_platform=os.getenv('NACL_TARGET_PLATFORM'),
+ install_subdir='load_progress', lib_prefix='..')
+nacl_env.Append(
+ # Add a CPPPATH that enables the full-path #include directives, such as
+ # #include "examples/sine_synth/sine_synth.h"
+ CPPPATH=[os.path.dirname(os.path.dirname(os.getcwd()))],
+ # Strict ANSI compliance.
+ CCFLAGS=['-pedantic', '-Werror'],
+ )
+
+sources = ['load_progress.cc']
+
+opt_nexes, dbg_nexes = nacl_env.AllNaClModules(sources, 'load_progress')
+
+# This target is used by the SDK build system to provide a prebuilt version
+# of the example in the SDK installer.
+nacl_env.InstallPrebuilt('load_progress')
+
+common_files = [
+ 'check_browser.js',
+ ]
+common_files = [
+ os.path.join(os.path.dirname(os.getcwd()), 'common', common_file)
+ for common_file in common_files]
+
+app_files = [
+ 'load_progress.html',
+ 'load_progress.nmf',
+ ]
+
+# Split the install of the .nexes from the other app sources so that the strip
+# action is applied to the .nexes only.
+install_nexes = nacl_env.NaClStrippedInstall(dir=nacl_env['NACL_INSTALL_ROOT'],
+ source=opt_nexes)
+install_app = nacl_env.Install(dir=nacl_env['NACL_INSTALL_ROOT'],
+ source=app_files)
+common_dir = os.path.join(os.path.dirname(nacl_env['NACL_INSTALL_ROOT']),
+ 'common')
+install_common = nacl_env.Install(dir=common_dir, source=common_files)
+nacl_env.Alias('install',
+ source=[install_app, install_common, install_nexes])
diff --git a/native_client_sdk/src/examples/load_progress/load_progress.cc b/native_client_sdk/src/examples/load_progress/load_progress.cc
new file mode 100644
index 0000000..07ce440
--- /dev/null
+++ b/native_client_sdk/src/examples/load_progress/load_progress.cc
@@ -0,0 +1,69 @@
+// Copyright (c) 2011 The Native Client Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/// @file
+/// This example demonstrates loading and running a very simple NaCl
+/// module. To load the NaCl module, the browser first looks for the
+/// CreateModule() factory method (at the end of this file). It calls
+/// CreateModule() once to load the module code from your .nexe. After the
+/// .nexe code is loaded, CreateModule() is not called again.
+///
+/// Once the .nexe code is loaded, the browser then calls the
+/// LoadProgressModule::CreateInstance()
+/// method on the object returned by CreateModule(). It calls CreateInstance()
+/// each time it encounters an <embed> tag that references your NaCl module.
+
+#include "ppapi/cpp/instance.h"
+#include "ppapi/cpp/module.h"
+#include "ppapi/cpp/var.h"
+
+namespace load_progress {
+/// The Instance class. One of these exists for each instance of your NaCl
+/// module on the web page. The browser will ask the Module object to create
+/// a new Instance for each occurrence of the <embed> tag that has these
+/// attributes:
+/// <pre>
+/// type="application/x-nacl"
+/// nacl="hello_world.nmf"
+/// </pre>
+class LoadProgressInstance : public pp::Instance {
+ public:
+ explicit LoadProgressInstance(PP_Instance instance)
+ : pp::Instance(instance) {}
+ virtual ~LoadProgressInstance() {}
+};
+
+/// The Module class. The browser calls the CreateInstance() method to create
+/// an instance of your NaCl module on the web page. The browser creates a new
+/// instance for each <embed> tag with
+/// <code>type="application/x-nacl"</code>.
+class LoadProgressModule : public pp::Module {
+ public:
+ LoadProgressModule() : pp::Module() {}
+ virtual ~LoadProgressModule() {}
+
+ /// Create and return a HelloWorldInstance object.
+ /// @param[in] instance a handle to a plug-in instance.
+ /// @return a newly created HelloWorldInstance.
+ /// @note The browser is responsible for calling @a delete when done.
+ virtual pp::Instance* CreateInstance(PP_Instance instance) {
+ return new LoadProgressInstance(instance);
+ }
+};
+} // namespace load_progress
+
+
+namespace pp {
+/// Factory function called by the browser when the module is first loaded.
+/// The browser keeps a singleton of this module. It calls the
+/// CreateInstance() method on the object you return to make instances. There
+/// is one instance per <embed> tag on the page. This is the main binding
+/// point for your NaCl module with the browser.
+/// @return new LoadProgressModule.
+/// @note The browser is responsible for deleting returned @a Module.
+Module* CreateModule() {
+ return new load_progress::LoadProgressModule();
+}
+} // namespace pp
+
diff --git a/native_client_sdk/src/examples/load_progress/load_progress.html b/native_client_sdk/src/examples/load_progress/load_progress.html
new file mode 100644
index 0000000..4c2dc47
--- /dev/null
+++ b/native_client_sdk/src/examples/load_progress/load_progress.html
@@ -0,0 +1,232 @@
+<!DOCTYPE html>
+<html>
+ <!--
+ Copyright (c) 2011 The Native Client Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style license that can be
+ found in the LICENSE file.
+ -->
+<head>
+ <title>Load Progress Example</title>
+ <script type="text/javascript" src="../common/check_browser.js"></script>
+ <script type="text/javascript">
+ // Check for Native Client support in the browser before the DOM loads.
+ var isValidBrowser = false;
+ var browserSupportStatus = 0;
+ var checker = new browser_version.BrowserChecker(
+ 14, // Minumum Chrome version.
+ navigator["appVersion"],
+ navigator["plugins"]);
+ checker.checkBrowser();
+
+ loadProgressModule = null; // Global application object.
+ statusText = 'NO-STATUS';
+
+ isValidBrowser = checker.getIsValidBrowser();
+ browserSupportStatus = checker.getBrowserSupportStatus();
+
+ // Handler that gets called when the NaCl module starts loading. This
+ // event is always triggered when an <EMBED> tag has a MIME type of
+ // application/x-nacl.
+ function moduleDidStartLoad() {
+ appendToEventLog('loadstart');
+ }
+
+ // Progress event handler. |event| contains a couple of interesting
+ // properties that are used in this example:
+ // total The size of the NaCl module in bytes. Note that this value
+ // is 0 until |lengthComputable| is true. In particular, this
+ // value is 0 for the first 'progress' event.
+ // loaded The number of bytes loaded so far.
+ // lengthComputable A boolean indicating that the |total| field
+ // represents a valid length.
+ //
+ // event The ProgressEvent that triggered this handler.
+ function moduleLoadProgress(event) {
+ var loadPercent = 0.0;
+ var loadPercentString;
+ if (event.lengthComputable && event.total > 0) {
+ loadPercent = event.loaded / event.total * 100.0;
+ loadPercentString = loadPercent + '%';
+ } else {
+ // The total length is not yet known.
+ loadPercent = -1.0;
+ loadPercentString = 'Computing...';
+ }
+ appendToEventLog('progress: ' + loadPercentString +
+ ' (' + event.loaded + ' of ' + event.total + ' bytes)');
+ }
+
+ // Handler that gets called if an error occurred while loading the NaCl
+ // module. Note that the event does not carry any meaningful data about
+ // the error, you have to check lastError on the <EMBED> element to find
+ // out what happened.
+ function moduleLoadError() {
+ appendToEventLog('error: ' + loadProgressModule.lastError);
+ }
+
+ // Handler that gets called if the NaCl module load is aborted.
+ function moduleLoadAbort() {
+ appendToEventLog('abort');
+ }
+
+ // When the NaCl module has loaded indicate success.
+ function moduleDidLoad() {
+ loadProgressModule = document.getElementById('load_progress');
+ appendToEventLog('load');
+ updateStatus('SUCCESS');
+ }
+
+ // Handler that gets called when the NaCl module loading has completed.
+ // You will always get one of these events, regardless of whether the NaCl
+ // module loaded successfully or not. For example, if there is an error
+ // during load, you will get an 'error' event and a 'loadend' event. Note
+ // that if the NaCl module loads successfully, you will get both a 'load'
+ // event and a 'loadend' event.
+ function moduleDidEndLoad() {
+ appendToEventLog('loadend');
+ var lastError = event.target.lastError;
+ if (lastError == undefined || lastError.length == 0) {
+ lastError = '&lt;none&gt;';
+ }
+ appendToEventLog('lastError: ' + lastError);
+ }
+
+
+ // Handle a message coming from the NaCl module.
+ function handleMessage(message_event) {
+ alert(message_event.data);
+ }
+
+ // Set the global status message. Updates the 'status_field' element with
+ // the new text.
+ // opt_message The message text. If this is null or undefined, then
+ // attempt to set the element with id 'status_field' to the value of
+ // |statusText|.
+ function updateStatus(opt_message) {
+ if (opt_message)
+ statusText = opt_message;
+ var statusField = document.getElementById('status_field');
+ if (statusField) {
+ statusField.innerHTML = statusText;
+ }
+ }
+
+ // Append an event name to the 'event_log_field' element. Event names
+ // are separated by a <br> tag so they get listed one per line.
+ // logMessage The message to append to the log.
+ function appendToEventLog(logMessage) {
+ var eventLogField = document.getElementById('event_log_field');
+ if (eventLogField.innerHTML.length == 0) {
+ eventLogField.innerHTML = logMessage;
+ } else {
+ eventLogField.innerHTML = eventLogField.innerHTML +
+ '<br />' +
+ logMessage;
+ }
+ }
+ </script>
+</head>
+<body>
+
+<h1>Native Client Load Event Example</h1>
+
+<h2>Event Log</h2>
+<div id="event_log_field"></div>
+<h2>Status</h2>
+<div id="status_field">NO-STATUS</div>
+
+<div id="listener">
+ <script type="text/javascript">
+ var listener = document.getElementById('listener');
+ listener.addEventListener('loadstart', moduleDidStartLoad, true);
+ listener.addEventListener('progress', moduleLoadProgress, true);
+ listener.addEventListener('error', moduleLoadError, true);
+ listener.addEventListener('abort', moduleLoadAbort, true);
+ listener.addEventListener('load', moduleDidLoad, true);
+ listener.addEventListener('loadend', moduleDidEndLoad, true);
+ listener.addEventListener('message', handleMessage, true);
+
+ switch (browserSupportStatus) {
+ case browser_version.BrowserChecker.StatusValues.NACL_ENABLED:
+ appendToEventLog('Native Client plugin enabled.');
+ break;
+ case browser_version.BrowserChecker.StatusValues.UNKNOWN_BROWSER:
+ updateStatus('UNKNOWN BROWSER');
+ break;
+ case browser_version.BrowserChecker.StatusValues.CHROME_VERSION_TOO_OLD:
+ appendToEventLog(
+ 'Chrome too old: You must use Chrome version 14 or later.');
+ updateStatus('NEED CHROME 14 OR LATER');
+ break;
+ case browser_version.BrowserChecker.StatusValues.NACL_NOT_ENABLED:
+ appendToEventLog(
+ 'NaCl disabled: Native Client is not enabled.<br>' +
+ 'Please go to <b>chrome://plugins</b> and enable Native Client ' +
+ 'plugin.');
+ updateStatus('NaCl NOT ENABLED');
+ break;
+ case browser_version.BrowserChecker.StatusValues.NOT_USING_SERVER:
+ appendToEventLog(
+ 'file: URL detected, please use a web server to host Native ' +
+ 'Client applications.');
+ updateStatus('NaCl NOT ENABLED');
+ default:
+ appendToEventLog('Unknown error: Unable to detect browser and/or ' +
+ 'Native Client support.');
+ updateStatus('UNKNOWN ERROR');
+ break;
+ }
+ </script>
+
+ <!-- Load the published .nexe. This includes the 'src' attribute which
+ shows how to load multi-architecture modules. Each entry in the "nexes"
+ object in the .nmf manifest file is a key-value pair: the key is the runtime
+ ('x86-32', 'x86-64', etc.); the value is a URL for the desired NaCl module.
+ To load the debug versions of your .nexes, set the 'src' attribute to the
+ _dbg.nmf version of the manifest file.
+
+ Note: The <EMBED> element is wrapped inside a <DIV>, which has both a 'load'
+ and a 'message' event listener attached. This wrapping method is used
+ instead of attaching the event listeners directly to the <EMBED> element to
+ ensure that the listeners are active before the NaCl module 'load' event
+ fires. This also allows you to use PPB_Messaging.PostMessage() (in C) or
+ pp::Instance.PostMessage() (in C++) from within the initialization code in
+ your NaCl module.
+ -->
+ <embed name="nacl_module"
+ id="load_progress"
+ width=0 height=0
+ src="load_progress.nmf"
+ type="application/x-nacl" />
+
+ <script type="text/javascript">
+ loadProgressModule = document.getElementById('load_progress');
+ // Futher diagnose NaCl loading.
+ if (loadProgressModule == null ||
+ typeof loadProgressModule.readyState == 'undefined') {
+ switch (browserSupportStatus) {
+ case browser_version.BrowserChecker.StatusValues.NACL_ENABLED:
+ // The NaCl plugin is enabled and running, it's likely that the flag
+ // isn't set.
+ appendToEventLog(
+ 'NaCl flag disabled: The Native Client flag is not enabled.<br>' +
+ 'Please go to <b>chrome://flags</b> enable Native Client and ' +
+ 'relaunch your browser. See also: ' +
+ '<a href="http://code.google.com/chrome/nativeclient/docs/' +
+ 'running.html">Running Web Applications that Use Native Client' +
+ '</a>');
+ updateStatus('NaCl NOT ENABLED');
+ break;
+ case browser_version.BrowserChecker.StatusValues.UNKNOWN_BROWSER:
+ appendToEventLog('Native Client applications are not supported by ' +
+ 'this browser.');
+ break;
+ default:
+ appendToEventLog('Unknown error when loading Native Client ' +
+ 'application.');
+ }
+ }
+ </script>
+</div>
+</body>
+</html>
diff --git a/native_client_sdk/src/examples/mouselock/build.scons b/native_client_sdk/src/examples/mouselock/build.scons
new file mode 100644
index 0000000..f56186e
--- /dev/null
+++ b/native_client_sdk/src/examples/mouselock/build.scons
@@ -0,0 +1,51 @@
+#! -*- python -*-
+#
+# Copyright (c) 2011 The Native Client Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import make_nacl_env
+import os
+
+nacl_env = make_nacl_env.NaClEnvironment(
+ use_c_plus_plus_libs=True, nacl_platform=os.getenv('NACL_TARGET_PLATFORM'),
+ install_subdir='mouselock', lib_prefix='..')
+nacl_env.Append(
+ # Add a CPPPATH that enables the full-path #include directives, such as
+ # #include "examples/sine_synth/sine_synth.h"
+ CPPPATH=[os.path.dirname(os.path.dirname(os.getcwd()))],
+ # Strict ANSI compliance.
+ CCFLAGS=['-pedantic', '-Werror'],
+ )
+
+sources = ['mouselock.cc']
+
+opt_nexes, dbg_nexes = nacl_env.AllNaClModules(sources, 'mouselock')
+
+# This target is used by the SDK build system to provide a prebuilt version
+# of the example in the SDK installer.
+nacl_env.InstallPrebuilt('mouselock')
+
+common_files = [
+ 'check_browser.js',
+ ]
+common_files = [
+ os.path.join(os.path.dirname(os.getcwd()), 'common', common_file)
+ for common_file in common_files]
+
+app_files = [
+ 'mouselock.html',
+ 'mouselock.nmf',
+ ]
+
+# Split the install of the .nexes from the other app sources so that the strip
+# action is applied to the .nexes only.
+install_nexes = nacl_env.NaClStrippedInstall(dir=nacl_env['NACL_INSTALL_ROOT'],
+ source=opt_nexes)
+install_app = nacl_env.Install(dir=nacl_env['NACL_INSTALL_ROOT'],
+ source=app_files)
+common_dir = os.path.join(os.path.dirname(nacl_env['NACL_INSTALL_ROOT']),
+ 'common')
+install_common = nacl_env.Install(dir=common_dir, source=common_files)
+nacl_env.Alias('install',
+ source=[install_app, install_common, install_nexes])
diff --git a/native_client_sdk/src/examples/mouselock/mouselock.cc b/native_client_sdk/src/examples/mouselock/mouselock.cc
new file mode 100644
index 0000000..b0e18fd
--- /dev/null
+++ b/native_client_sdk/src/examples/mouselock/mouselock.cc
@@ -0,0 +1,329 @@
+// Copyright (c) 2011 The Native Client 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 "examples/mouselock/mouselock.h"
+
+#include <cmath>
+#include <cstdlib>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+
+// Indicate the direction of the mouse location relative to the center of the
+// view. These values are used to determine which 2D quadrant the needle lies
+// in.
+typedef enum {
+ kLeft = 0,
+ kRight = 1,
+ kUp = 2,
+ kDown = 3
+} MouseDirection;
+
+namespace {
+const int kCentralSpotRadius = 5;
+const uint32_t kReturnKeyCode = 13;
+const uint32_t kBackgroundColor = 0xff606060;
+const uint32_t kLockedForegroundColor = 0xfff08080;
+const uint32_t kUnlockedForegroundColor = 0xff80f080;
+} // namespace
+
+namespace mouselock {
+
+MouseLockInstance::~MouseLockInstance() {
+ free(background_scanline_);
+ background_scanline_ = NULL;
+}
+
+bool MouseLockInstance::Init(uint32_t argc,
+ const char* argn[],
+ const char* argv[]) {
+ RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE |
+ PP_INPUTEVENT_CLASS_KEYBOARD);
+ return true;
+}
+
+bool MouseLockInstance::HandleInputEvent(const pp::InputEvent& event) {
+ switch (event.GetType()) {
+ case PP_INPUTEVENT_TYPE_MOUSEDOWN: {
+ is_context_bound_ = false;
+ if (fullscreen_.IsFullscreen()) {
+ // Leaving fullscreen mode also unlocks the mouse if it was locked.
+ // In this case, the browser will call MouseLockLost() on this
+ // instance.
+ if (!fullscreen_.SetFullscreen(false)) {
+ Log("Could not leave fullscreen mode\n");
+ }
+ } else {
+ if (!fullscreen_.SetFullscreen(true)) {
+ Log("Could not set fullscreen mode\n");
+ } else {
+ pp::MouseInputEvent mouse_event(event);
+ if (mouse_event.GetButton() == PP_INPUTEVENT_MOUSEBUTTON_LEFT &&
+ !mouse_locked_) {
+ LockMouse(callback_factory_.NewRequiredCallback(
+ &MouseLockInstance::DidLockMouse));
+ }
+ }
+ }
+ return true;
+ }
+
+ case PP_INPUTEVENT_TYPE_MOUSEMOVE: {
+ pp::MouseInputEvent mouse_event(event);
+ mouse_movement_ = mouse_event.GetMovement();
+ Paint();
+ return true;
+ }
+
+ case PP_INPUTEVENT_TYPE_KEYDOWN: {
+ pp::KeyboardInputEvent key_event(event);
+ // Lock the mouse when the Enter key is pressed.
+ if (key_event.GetKeyCode() == kReturnKeyCode) {
+ if (mouse_locked_) {
+ UnlockMouse();
+ } else {
+ LockMouse(callback_factory_.NewRequiredCallback(
+ &MouseLockInstance::DidLockMouse));
+ }
+ }
+ return true;
+ }
+
+ case PP_INPUTEVENT_TYPE_MOUSEUP:
+ case PP_INPUTEVENT_TYPE_MOUSEENTER:
+ case PP_INPUTEVENT_TYPE_MOUSELEAVE:
+ case PP_INPUTEVENT_TYPE_WHEEL:
+ case PP_INPUTEVENT_TYPE_RAWKEYDOWN:
+ case PP_INPUTEVENT_TYPE_KEYUP:
+ case PP_INPUTEVENT_TYPE_CHAR:
+ case PP_INPUTEVENT_TYPE_CONTEXTMENU:
+ case PP_INPUTEVENT_TYPE_IME_COMPOSITION_START:
+ case PP_INPUTEVENT_TYPE_IME_COMPOSITION_UPDATE:
+ case PP_INPUTEVENT_TYPE_IME_COMPOSITION_END:
+ case PP_INPUTEVENT_TYPE_IME_TEXT:
+ case PP_INPUTEVENT_TYPE_UNDEFINED:
+ default:
+ return false;
+ }
+}
+
+void MouseLockInstance::DidChangeView(const pp::Rect& position,
+ const pp::Rect& clip) {
+ int width = position.size().width();
+ int height = position.size().height();
+
+ // When entering into full-screen mode, DidChangeView() gets called twice.
+ // The first time, any 2D context will fail to bind to this pp::Instacne.
+ if (width == width_ && height == height_ && is_context_bound_) {
+ return;
+ }
+ width_ = width;
+ height_ = height;
+
+ device_context_ = pp::Graphics2D(this, pp::Size(width_, height_), false);
+ waiting_for_flush_completion_ = false;
+ free(background_scanline_);
+ background_scanline_ = NULL;
+ is_context_bound_ = BindGraphics(device_context_);
+ if (!is_context_bound_) {
+ Log("Could not bind to 2D context\n");
+ return;
+ }
+ background_scanline_ = static_cast<uint32_t*>(
+ malloc(width_ * sizeof(*background_scanline_)));
+ uint32_t* bg_pixel = background_scanline_;
+ for (int x = 0; x < width_; ++x) {
+ *bg_pixel++ = kBackgroundColor;
+ }
+ Paint();
+}
+
+void MouseLockInstance::MouseLockLost() {
+ if (mouse_locked_) {
+ mouse_locked_ = false;
+ Paint();
+ } else {
+ PP_NOTREACHED();
+ }
+}
+
+void MouseLockInstance::DidLockMouse(int32_t result) {
+ mouse_locked_ = result == PP_OK;
+ mouse_movement_.set_x(0);
+ mouse_movement_.set_y(0);
+ Paint();
+}
+
+void MouseLockInstance::DidFlush(int32_t result) {
+ waiting_for_flush_completion_ = false;
+}
+
+void MouseLockInstance::Paint() {
+ if (waiting_for_flush_completion_) {
+ return;
+ }
+ pp::ImageData image = PaintImage(width_, height_);
+ if (image.is_null()) {
+ Log("Could not create image data\n");
+ return;
+ }
+ device_context_.ReplaceContents(&image);
+ waiting_for_flush_completion_ = true;
+ device_context_.Flush(
+ callback_factory_.NewRequiredCallback(&MouseLockInstance::DidFlush));
+}
+
+pp::ImageData MouseLockInstance::PaintImage(int width, int height) {
+ pp::ImageData image(this, PP_IMAGEDATAFORMAT_BGRA_PREMUL,
+ pp::Size(width, height), false);
+ if (image.is_null() || image.data() == NULL)
+ return image;
+
+ ClearToBackground(&image);
+ uint32_t foreground_color = mouse_locked_ ? kLockedForegroundColor :
+ kUnlockedForegroundColor;
+ DrawCenterSpot(&image, foreground_color);
+ DrawNeedle(&image, foreground_color);
+ return image;
+}
+
+void MouseLockInstance::ClearToBackground(pp::ImageData* image) {
+ if (image == NULL) {
+ Log("ClearToBackground with NULL image");
+ return;
+ }
+ if (background_scanline_ == NULL)
+ return;
+ int image_height = image->size().height();
+ int image_width = image->size().width();
+ for (int y = 0; y < image_height; ++y) {
+ uint32_t* scanline = image->GetAddr32(pp::Point(0, y));
+ memcpy(scanline,
+ background_scanline_,
+ image_width * sizeof(*background_scanline_));
+ }
+}
+
+void MouseLockInstance::DrawCenterSpot(pp::ImageData* image,
+ uint32_t spot_color) {
+ if (image == NULL) {
+ Log("DrawCenterSpot with NULL image");
+ return;
+ }
+ // Draw the center spot. The ROI is bounded by the size of the spot, plus
+ // one pixel.
+ int center_x = image->size().width() / 2;
+ int center_y = image->size().height() / 2;
+ int region_of_interest_radius = kCentralSpotRadius + 1;
+
+ for (int y = center_y - region_of_interest_radius;
+ y < center_y + region_of_interest_radius;
+ ++y) {
+ for (int x = center_x - region_of_interest_radius;
+ x < center_x + region_of_interest_radius;
+ ++x) {
+ if (GetDistance(x, y, center_x, center_y) < kCentralSpotRadius) {
+ *image->GetAddr32(pp::Point(x, y)) = spot_color;
+ }
+ }
+ }
+}
+
+void MouseLockInstance::DrawNeedle(pp::ImageData* image,
+ uint32_t needle_color) {
+ if (image == NULL) {
+ Log("DrawNeedle with NULL image");
+ return;
+ }
+ if (GetDistance(mouse_movement_.x(), mouse_movement_.y(), 0, 0) <=
+ kCentralSpotRadius) {
+ return;
+ }
+
+ int abs_mouse_x = std::abs(mouse_movement_.x());
+ int abs_mouse_y = std::abs(mouse_movement_.y());
+ int center_x = image->size().width() / 2;
+ int center_y = image->size().height() / 2;
+ pp::Point vertex(mouse_movement_.x() + center_x,
+ mouse_movement_.y() + center_y);
+ pp::Point anchor_1;
+ pp::Point anchor_2;
+ MouseDirection direction = kLeft;
+
+ if (abs_mouse_x >= abs_mouse_y) {
+ anchor_1.set_x(center_x);
+ anchor_1.set_y(center_y - kCentralSpotRadius);
+ anchor_2.set_x(center_x);
+ anchor_2.set_y(center_y + kCentralSpotRadius);
+ direction = (mouse_movement_.x() < 0) ? kLeft : kRight;
+ if (direction == kLeft)
+ anchor_1.swap(anchor_2);
+ } else {
+ anchor_1.set_x(center_x + kCentralSpotRadius);
+ anchor_1.set_y(center_y);
+ anchor_2.set_x(center_x - kCentralSpotRadius);
+ anchor_2.set_y(center_y);
+ direction = (mouse_movement_.y() < 0) ? kUp : kDown;
+ if (direction == kUp)
+ anchor_1.swap(anchor_2);
+ }
+
+ for (int y = center_y - abs_mouse_y; y < center_y + abs_mouse_y; ++y) {
+ for (int x = center_x - abs_mouse_x; x < center_x + abs_mouse_x; ++x) {
+ bool within_bound_1 =
+ ((y - anchor_1.y()) * (vertex.x() - anchor_1.x())) >
+ ((vertex.y() - anchor_1.y()) * (x - anchor_1.x()));
+ bool within_bound_2 =
+ ((y - anchor_2.y()) * (vertex.x() - anchor_2.x())) <
+ ((vertex.y() - anchor_2.y()) * (x - anchor_2.x()));
+ bool within_bound_3 =
+ (direction == kUp && y < center_y) ||
+ (direction == kDown && y > center_y) ||
+ (direction == kLeft && x < center_x) ||
+ (direction == kRight && x > center_x);
+
+ if (within_bound_1 && within_bound_2 && within_bound_3) {
+ *image->GetAddr32(pp::Point(x, y)) = needle_color;
+ }
+ }
+ }
+}
+
+
+void MouseLockInstance::Log(const char* format, ...) {
+ va_list args;
+ va_start(args, format);
+ char buf[512];
+ vsnprintf(buf, sizeof(buf) - 1, format, args);
+ buf[sizeof(buf) - 1] = '\0';
+ va_end(args);
+
+ pp::Var value(buf);
+ PostMessage(value);
+}
+
+} // namespace mouselock
+
+// This object is the global object representing this plugin library as long
+// as it is loaded.
+class MouseLockModule : public pp::Module {
+ public:
+ MouseLockModule() : pp::Module() {}
+ virtual ~MouseLockModule() {}
+
+ // Override CreateInstance to create your customized Instance object.
+ virtual pp::Instance* CreateInstance(PP_Instance instance) {
+ return new mouselock::MouseLockInstance(instance);
+ }
+};
+
+namespace pp {
+
+// Factory function for your specialization of the Module object.
+Module* CreateModule() {
+ return new MouseLockModule();
+}
+
+} // namespace pp
+
diff --git a/native_client_sdk/src/examples/mouselock/mouselock.h b/native_client_sdk/src/examples/mouselock/mouselock.h
new file mode 100644
index 0000000..88650d6
--- /dev/null
+++ b/native_client_sdk/src/examples/mouselock/mouselock.h
@@ -0,0 +1,102 @@
+// Copyright (c) 2011 The Native Client 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 <cmath>
+
+#include "ppapi/c/ppb_fullscreen.h"
+#include "ppapi/c/ppb_input_event.h"
+#include "ppapi/cpp/completion_callback.h"
+#include "ppapi/cpp/fullscreen.h"
+#include "ppapi/cpp/mouse_lock.h"
+#include "ppapi/cpp/graphics_2d.h"
+#include "ppapi/cpp/image_data.h"
+#include "ppapi/cpp/input_event.h"
+#include "ppapi/cpp/instance.h"
+#include "ppapi/cpp/module.h"
+#include "ppapi/cpp/rect.h"
+#include "ppapi/cpp/var.h"
+
+namespace mouselock {
+
+class MouseLockInstance : public pp::Instance, public pp::MouseLock {
+ public:
+ explicit MouseLockInstance(PP_Instance instance)
+ : pp::Instance(instance),
+ pp::MouseLock(this),
+ width_(0),
+ height_(0),
+ mouse_locked_(false),
+ waiting_for_flush_completion_(false),
+ callback_factory_(this),
+ fullscreen_(this),
+ is_context_bound_(false),
+ background_scanline_(NULL) {
+ }
+ virtual ~MouseLockInstance();
+
+ // Called by the browser when the NaCl module is loaded and all ready to go.
+ virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]);
+
+ // Called by the browser to handle incoming input events.
+ virtual bool HandleInputEvent(const pp::InputEvent& event);
+
+ // Called whenever the in-browser window changes size.
+ virtual void DidChangeView(const pp::Rect& position, const pp::Rect& clip);
+
+ // Called by the browser when mouselock is lost. This happens when the NaCl
+ // module exits fullscreen mode.
+ virtual void MouseLockLost();
+
+ private:
+ // Return the Cartesian distance between two points.
+ double GetDistance(int point_1_x, int point_1_y,
+ int point_2_x, int point_2_y) {
+ return sqrt(pow(static_cast<double>(point_1_x - point_2_x), 2) +
+ pow(static_cast<double>(point_1_y - point_2_y), 2));
+ }
+
+ // Called when mouse lock has been acquired. Used as a callback to
+ // pp::MouseLock.LockMouse().
+ void DidLockMouse(int32_t result);
+
+ // Called when the 2D context has been flushed to the browser window. Used
+ // as a callback to pp::Graphics2D.Flush().
+ void DidFlush(int32_t result);
+
+ // Creates a new paint buffer, paints it then flush it to the 2D context. If
+ // a flush is pending, this does nothing.
+ void Paint();
+
+ // Create a new pp::ImageData and paint the graphics that represent the mouse
+ // movement in it. Return the new pp::ImageData.
+ pp::ImageData PaintImage(int width, int height);
+
+ // Fill the image with the backgroud color.
+ void ClearToBackground(pp::ImageData* image);
+
+ // Draw a spot in |spot_color| in the center of the image. The radius of the
+ // spot is defined by a constant value in mouselock.cc
+ void DrawCenterSpot(pp::ImageData* image, uint32_t spot_color);
+
+ // Draw the needle when the mouse is outside of the central spot.
+ void DrawNeedle(pp::ImageData* image, uint32_t needle_color);
+
+ // Print the printf-style format to the "console" via PostMessage.
+ void Log(const char* format, ...);
+
+ int width_;
+ int height_;
+
+ bool mouse_locked_;
+ pp::Point mouse_movement_;
+ bool waiting_for_flush_completion_;
+ pp::CompletionCallbackFactory<MouseLockInstance> callback_factory_;
+
+ pp::Fullscreen fullscreen_;
+ pp::Graphics2D device_context_;
+ bool is_context_bound_;
+ uint32_t* background_scanline_;
+};
+
+} // namespace mouselock
diff --git a/native_client_sdk/src/examples/mouselock/mouselock.html b/native_client_sdk/src/examples/mouselock/mouselock.html
new file mode 100644
index 0000000..eaa44dc
--- /dev/null
+++ b/native_client_sdk/src/examples/mouselock/mouselock.html
@@ -0,0 +1,108 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+<!--
+Copyright (c) 2011 The Native Client Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+<head>
+ <meta http-equiv="Pragma" content="no-cache" />
+ <meta http-equiv="Expires" content="-1" />
+ <script type="text/javascript" src="../common/check_browser.js"></script>
+ <script>
+ // Check for Native Client support in the browser before the DOM loads.
+ var isValidBrowser = false;
+ var browserSupportStatus = 0;
+ // Fullscreen and mouselock support is in Chrome version 16.
+ var CHROME_MINIMUM_VERSION = 16;
+
+ var checker = new browser_version.BrowserChecker(
+ CHROME_MINIMUM_VERSION,
+ navigator["appVersion"],
+ navigator["plugins"]);
+ checker.checkBrowser();
+
+ isValidBrowser = checker.getIsValidBrowser();
+ browserSupportStatus = checker.getBrowserSupportStatus();
+
+ function handleMessage(message_event) {
+ console.log(message_event.data);
+ }
+ </script>
+ <title>Full-screen and Mouse-lock Example</title>
+</head>
+<body title="This tooltip should not be shown if the mouse is locked.">
+ <h1>Full-screen and Mouse-lock Example</h1>
+ <ul>
+ <li>There are two different kinds of fullscreen mode: "tab fullscreen" and
+ "browser fullscreen".
+ <ul>
+ <li>Tab fullscreen refers to when a tab enters fullscreen mode via the
+ JS or Pepper fullscreen API.</li>
+ <li>Browser fullscreen refers to the user putting the browser itself
+ into fullscreen mode from the UI (e.g., pressing F11).</li>
+ </ul>
+ <span style="font-weight:bold">
+ NOTE: Mouse lock is only allowed in "tab fullscreen" mode.
+ </span>
+ </li>
+ <li>Lock mouse:
+ <ul>
+ <li>left click in the grey box; or</li>
+ <li>right click in the box to ensure that it is focused and
+ then press Enter key. (You could verify that the tooltip window is
+ dismissed properly by this second approach.)</li>
+ </ul>
+ </li>
+ <li>Unlock mouse voluntarily (i.e., NaCl module unlocks mouse):
+ <ul>
+ <li>press Enter.</li>
+ </ul>
+ </li>
+ <li>Unlock mouse involuntarily (i.e. Chrome unlocks mouse):
+ <ul>
+ <li>lose focus; or</li>
+ <li>press Esc key; or</li>
+ <li>exit from the "tab fullscreen" mode.</li>
+ </ul>
+ </li>
+ </ul>
+ <p>Clicking the mouse inside the grey square takes the NaCl module to/from
+ combined fullscreen and mouselock mode.</p>
+ <p>While in fullscreen, pressing Enter will exit/enter mouse lock mode.</p>
+ <!-- Load the published .nexe. This includes the 'src' attribute which
+ shows how to load multi-architecture modules. Each entry in the "nexes"
+ object in the .nmf manifest file is a key-value pair: the key is the runtime
+ ('x86-32', 'x86-64', etc.); the value is a URL for the desired NaCl module.
+ To load the debug versions of your .nexes, set the 'src' attribute to the
+ _dbg.nmf version of the manifest file.
+
+ Note: The <EMBED> element is wrapped inside a <DIV>, which has both a 'load'
+ and a 'message' event listener attached. This wrapping method is used
+ instead of attaching the event listeners directly to the <EMBED> element to
+ ensure that the listeners are active before the NaCl module 'load' event
+ fires. This also allows you to use PPB_Messaging.PostMessage() (in C) or
+ pp::Instance.PostMessage() (in C++) from within the initialization code in
+ your NaCl module.
+ -->
+ <div id="listener">
+ <script type="text/javascript">
+ if (browserSupportStatus ==
+ browser_version.BrowserChecker.StatusValues.CHROME_VERSION_TOO_OLD) {
+ alert('This example will only work on Chrome version ' +
+ CHROME_MINIMUM_VERSION +
+ ' or later.');
+ } else {
+ var listener = document.getElementById('listener')
+ listener.addEventListener('message', handleMessage, true);
+ // Create two instances of the NaCl module.
+ listener.innerHTML = '<embed id="mouselock_view" ' +
+ 'type="application/x-nacl" ' +
+ 'src="mouselock.nmf" ' +
+ 'width="300" height="300" />';
+ }
+ </script>
+ </div>
+</body>
+</html>
diff --git a/native_client_sdk/src/examples/multithreaded_input_events/build.scons b/native_client_sdk/src/examples/multithreaded_input_events/build.scons
new file mode 100644
index 0000000..00da894
--- /dev/null
+++ b/native_client_sdk/src/examples/multithreaded_input_events/build.scons
@@ -0,0 +1,41 @@
+#! -*- python -*-
+#
+# Copyright (c) 2011 The Native Client Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import make_nacl_env
+import nacl_utils
+import os
+
+nacl_env = make_nacl_env.NaClEnvironment(
+ use_c_plus_plus_libs=True, nacl_platform=os.getenv('NACL_TARGET_PLATFORM'),
+ install_subdir='multithreaded_input_events', lib_prefix='..')
+nacl_env.Append(
+ # Add a CPPPATH that enables the full-path #include directives, such as
+ # #include "examples/sine_synth/sine_synth.h"
+ CPPPATH=[os.path.dirname(os.path.dirname(os.getcwd()))],
+ # Strict ANSI compliance.
+ EXTRA_CCFLAGS=['-pedantic'],
+ )
+
+sources = ['mt_input_events.cc', 'custom_events.cc']
+
+opt_nexes, dbg_nexes = nacl_env.AllNaClModules(sources, 'mt_input_events')
+
+# This target is used by the SDK build system to provide a prebuilt version
+# of the example in the SDK installer.
+nacl_env.InstallPrebuilt('mt_input_events')
+
+app_files = [
+ 'mt_input_events.html',
+ 'mt_input_events.nmf',
+ ]
+
+# Split the install of the .nexes from the other app sources so that the strip
+# action is applied to the .nexes only.
+install_nexes = nacl_env.NaClStrippedInstall(dir=nacl_env['NACL_INSTALL_ROOT'],
+ source=opt_nexes)
+install_app = nacl_env.Install(dir=nacl_env['NACL_INSTALL_ROOT'],
+ source=app_files)
+nacl_env.Alias('install', source=install_app + install_nexes)
diff --git a/native_client_sdk/src/examples/multithreaded_input_events/custom_events.cc b/native_client_sdk/src/examples/multithreaded_input_events/custom_events.cc
new file mode 100644
index 0000000..b281da5
--- /dev/null
+++ b/native_client_sdk/src/examples/multithreaded_input_events/custom_events.cc
@@ -0,0 +1,111 @@
+// Copyright (c) 2011 The Native Client 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 <sstream>
+
+#include "examples/multithreaded_input_events/custom_events.h"
+
+namespace event_queue {
+
+// Convert a given modifier to a descriptive string. Note that the actual
+// declared type of modifier in each of the event classes is uint32_t, but it is
+// expected to be interpreted as a bitfield of 'or'ed PP_InputEvent_Modifier
+// values.
+std::string ModifierToString(uint32_t modifier) {
+ std::string s;
+ if (modifier & kShiftKeyModifier) {
+ s += "shift ";
+ }
+ if (modifier & kControlKeyModifier) {
+ s += "ctrl ";
+ }
+ if (modifier & kAltKeyModifier) {
+ s += "alt ";
+ }
+ if (modifier & kMetaKeyModifer) {
+ s += "meta ";
+ }
+ if (modifier & kKeyPadModifier) {
+ s += "keypad ";
+ }
+ if (modifier & kAutoRepeatModifier) {
+ s += "autorepeat ";
+ }
+ if (modifier & kLeftButtonModifier) {
+ s += "left-button-down ";
+ }
+ if (modifier & kMiddleButtonModifier) {
+ s += "middle-button-down ";
+ }
+ if (modifier & kRightButtonModifier) {
+ s += "right-button-down ";
+ }
+ if (modifier & kCapsLockModifier) {
+ s += "caps-lock ";
+ }
+ if (modifier & kNumLockModifier) {
+ s += "num-lock ";
+ }
+ return s;
+}
+
+
+std::string KeyEvent::ToString() const {
+ std::ostringstream stream;
+ stream << " Key event:"
+ << " modifier:" << string_event_modifiers()
+ << " key_code:" << key_code_
+ << " time:" << timestamp_
+ << " text:" << text_
+ << "\n";
+ return stream.str();
+}
+
+std::string MouseEvent::ToString() const {
+ std::ostringstream stream;
+ stream << " Mouse event:"
+ << " modifier:" << string_event_modifiers()
+ << " button:" << MouseButtonToString(mouse_button_)
+ << " x:" << x_position_
+ << " y:" << y_position_
+ << " click_count:" << click_count_
+ << " time:" << timestamp_
+ << "\n";
+ return stream.str();
+}
+
+std::string WheelEvent::ToString() const {
+ std::ostringstream stream;
+ stream << "Wheel event."
+ << " modifier:" << string_event_modifiers()
+ << " deltax:" << delta_x_
+ << " deltay:" << delta_y_
+ << " wheel_ticks_x:" << ticks_x_
+ << " wheel_ticks_y:" << ticks_y_
+ << " scroll_by_page: " << scroll_by_page_
+ << "\n";
+ return stream.str();
+}
+
+std::string MouseEvent::MouseButtonToString(MouseButton button) const {
+ switch (button) {
+ case kNone:
+ return "None";
+ case kLeft:
+ return "Left";
+ case kMiddle:
+ return "Middle";
+ case kRight:
+ return "Right";
+ default:
+ std::ostringstream stream;
+ stream << "Unrecognized ("
+ << static_cast<int32_t>(button)
+ << ")";
+ return stream.str();
+ }
+}
+
+} // end namespace
+
diff --git a/native_client_sdk/src/examples/multithreaded_input_events/custom_events.h b/native_client_sdk/src/examples/multithreaded_input_events/custom_events.h
new file mode 100644
index 0000000..029fb7a
--- /dev/null
+++ b/native_client_sdk/src/examples/multithreaded_input_events/custom_events.h
@@ -0,0 +1,133 @@
+// Copyright (c) 2011 The Native Client Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CUSTOM_EVENTS_H
+#define CUSTOM_EVENTS_H
+
+#include <stdint.h>
+#include <string>
+namespace event_queue {
+
+// These functions and classes are used to define a non-Pepper set of
+// events. This is typical of what many developers might do, since it
+// would be common to convert a Pepper event into some other more
+// application-specific type of event (SDL, Qt, etc.).
+
+// Constants used for Event::event_modifers_ (which is an int)
+// are given below. Use powers of 2 so we can use bitwise AND/OR operators.
+const uint32_t kShiftKeyModifier = 1 << 0;
+const uint32_t kControlKeyModifier = 1 << 1;
+const uint32_t kAltKeyModifier = 1 << 2;
+const uint32_t kMetaKeyModifer = 1 << 3;
+const uint32_t kKeyPadModifier = 1 << 4;
+const uint32_t kAutoRepeatModifier = 1 << 5;
+const uint32_t kLeftButtonModifier = 1 << 6;
+const uint32_t kMiddleButtonModifier = 1 << 7;
+const uint32_t kRightButtonModifier = 1 << 8;
+const uint32_t kCapsLockModifier = 1 << 9;
+const uint32_t kNumLockModifier = 1 << 10;
+
+std::string ModifierToString(uint32_t modifier);
+
+// Abstract base class for an Event -- ToString() is not defined.
+// With polymorphism, we can have a collection of Event* and call
+// ToString() on each one to be able to display the details of each
+// event.
+class Event {
+ public:
+ // Constructor for the base class.
+ // |modifiers| is an int that uses bit fields to set specific
+ // changes, such as the Alt key, specific button, etc. See
+ // ModifierToString() and constants defined in custom_events.cc.
+ explicit Event(uint32_t modifiers)
+ : event_modifiers_(modifiers) {}
+ uint32_t event_modifiers() const {return event_modifiers_;}
+ std::string string_event_modifiers() const {
+ return ModifierToString(event_modifiers_);
+ }
+ // Convert the WheelEvent to a string
+ virtual std::string ToString() const = 0;
+ virtual ~Event() {}
+
+ private:
+ uint32_t event_modifiers_;
+};
+
+// Class for a keyboard event.
+class KeyEvent : public Event {
+ public:
+ // KeyEvent Constructor. |modifiers| is passed to Event base class.
+ // |keycode| is the ASCII value, |time| is a timestamp,
+ // |text| is the value as a string.
+ KeyEvent(uint32_t modifiers, uint32_t keycode, double time,
+ std::string text) :
+ Event(modifiers), key_code_(keycode),
+ timestamp_(time), text_(text) {}
+ // Convert the WheelEvent to a string
+ virtual std::string ToString() const;
+
+ private:
+ uint32_t key_code_;
+ double timestamp_;
+ std::string text_;
+};
+class MouseEvent : public Event {
+ public:
+ // Specify a mouse button, with kNone available for initialization.
+ enum MouseButton {kNone, kLeft, kMiddle, kRight};
+
+ // MouseEvent Constructor. |modifiers| is passed to Event base class.
+ // |button| specifies which button
+ // |xpos| and |ypos| give the location,
+ // |clicks| is how many times this same |xpos|,|ypos|
+ // has been clicked in a row. |time| is a timestamp,
+ MouseEvent(uint32_t modifiers, MouseButton button, uint32_t xpos,
+ uint32_t ypos, uint32_t clicks, double time)
+ : Event(modifiers), mouse_button_(button),
+ x_position_(xpos), y_position_(ypos),
+ click_count_(clicks), timestamp_(time) {}
+ // Convert the WheelEvent to a string
+ virtual std::string ToString() const;
+
+ private:
+ MouseButton mouse_button_;
+ uint32_t x_position_;
+ uint32_t y_position_;
+ uint32_t click_count_;
+ double timestamp_;
+
+ std::string MouseButtonToString(MouseButton button) const;
+};
+
+
+class WheelEvent : public Event {
+ public:
+ // WheelEvent Constructor. |modifiers| is passed to Event base class.
+ // |xticks| and |yticks| specify number of mouse wheel ticks.
+ // |scroll_by_page| indicates if we have scrolled past the current
+ // page. |time| is a timestamp,
+ WheelEvent(int modifiers, uint32_t dx, uint32_t dy,
+ uint32_t xticks, uint32_t yticks, bool scroll_by_page,
+ float time) :
+ Event(modifiers), delta_x_(dx), delta_y_(dy),
+ ticks_x_(xticks), ticks_y_(yticks),
+ scroll_by_page_(scroll_by_page), timestamp_(time) {}
+ // Convert the WheelEvent to a string
+ virtual std::string ToString() const;
+
+ private:
+ uint32_t delta_x_;
+ uint32_t delta_y_;
+ uint32_t ticks_x_;
+ uint32_t ticks_y_;
+ bool scroll_by_page_;
+ double timestamp_;
+};
+
+
+
+} // end namespace
+
+#endif // CUSTOM_EVENTS_H
+
diff --git a/native_client_sdk/src/examples/multithreaded_input_events/mt_input_events.cc b/native_client_sdk/src/examples/multithreaded_input_events/mt_input_events.cc
new file mode 100644
index 0000000..a392ba2
--- /dev/null
+++ b/native_client_sdk/src/examples/multithreaded_input_events/mt_input_events.cc
@@ -0,0 +1,323 @@
+// Copyright (c) 2011 The Native Client Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// C headers
+#include <cassert>
+#include <cstdio>
+
+// C++ headers
+#include <sstream>
+#include <string>
+
+#include "examples/multithreaded_input_events/custom_events.h"
+#include "examples/multithreaded_input_events/shared_queue.h"
+#include "examples/multithreaded_input_events/thread_safe_ref_count.h"
+
+// NaCl
+#include "ppapi/cpp/completion_callback.h"
+#include "ppapi/cpp/input_event.h"
+#include "ppapi/cpp/instance.h"
+#include "ppapi/cpp/module.h"
+#include "ppapi/cpp/point.h"
+#include "ppapi/cpp/var.h"
+
+namespace event_queue {
+const char* const kDidChangeView = "DidChangeView";
+const char* const kHandleInputEvent = "DidHandleInputEvent";
+const char* const kDidChangeFocus = "DidChangeFocus";
+const char* const kHaveFocus = "HaveFocus";
+const char* const kDontHaveFocus = "DontHaveFocus";
+const char* const kCancelMessage = "CANCEL";
+
+// Convert a pepper inputevent modifier value into a
+// custom event modifier.
+unsigned int ConvertEventModifier(uint32_t pp_modifier) {
+ unsigned int custom_modifier = 0;
+ if (pp_modifier & PP_INPUTEVENT_MODIFIER_SHIFTKEY) {
+ custom_modifier |= kShiftKeyModifier;
+ }
+ if (pp_modifier & PP_INPUTEVENT_MODIFIER_CONTROLKEY) {
+ custom_modifier |= kControlKeyModifier;
+ }
+ if (pp_modifier & PP_INPUTEVENT_MODIFIER_ALTKEY) {
+ custom_modifier |= kAltKeyModifier;
+ }
+ if (pp_modifier & PP_INPUTEVENT_MODIFIER_METAKEY) {
+ custom_modifier |= kMetaKeyModifer;
+ }
+ if (pp_modifier & PP_INPUTEVENT_MODIFIER_ISKEYPAD) {
+ custom_modifier |= kKeyPadModifier;
+ }
+ if (pp_modifier & PP_INPUTEVENT_MODIFIER_ISAUTOREPEAT) {
+ custom_modifier |= kAutoRepeatModifier;
+ }
+ if (pp_modifier & PP_INPUTEVENT_MODIFIER_LEFTBUTTONDOWN) {
+ custom_modifier |= kLeftButtonModifier;
+ }
+ if (pp_modifier & PP_INPUTEVENT_MODIFIER_MIDDLEBUTTONDOWN) {
+ custom_modifier |= kMiddleButtonModifier;
+ }
+ if (pp_modifier & PP_INPUTEVENT_MODIFIER_RIGHTBUTTONDOWN) {
+ custom_modifier |= kRightButtonModifier;
+ }
+ if (pp_modifier & PP_INPUTEVENT_MODIFIER_CAPSLOCKKEY) {
+ custom_modifier |= kCapsLockModifier;
+ }
+ if (pp_modifier & PP_INPUTEVENT_MODIFIER_NUMLOCKKEY) {
+ custom_modifier |= kNumLockModifier;
+ }
+ return custom_modifier;
+}
+
+class EventInstance : public pp::Instance {
+ public:
+ explicit EventInstance(PP_Instance instance)
+ : pp::Instance(instance),
+ event_thread_(NULL),
+ callback_factory_(this) {
+ RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE | PP_INPUTEVENT_CLASS_WHEEL);
+ RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_KEYBOARD);
+ }
+
+ // Not guaranteed to be called in Pepper, but a good idea to cancel the
+ // queue and signal to workers to die if it is called.
+ virtual ~EventInstance() {
+ CancelQueueAndWaitForWorker();
+ }
+
+ // Create the 'worker thread'.
+ bool Init(uint32_t argc, const char* argn[], const char* argv[]) {
+ pthread_create(&event_thread_, NULL, ProcessEventOnWorkerThread, this);
+ return true;
+ }
+
+ /// Clicking outside of the instance's bounding box
+ /// will create a DidChangeFocus event (the NaCl instance is
+ /// out of focus). Clicking back inside the instance's
+ /// bounding box will create another DidChangeFocus event
+ /// (the NaCl instance is back in focus). The default is
+ /// that the instance is out of focus.
+ void DidChangeFocus(bool focus) {
+ PostMessage(pp::Var(kDidChangeFocus));
+ if (focus == true) {
+ PostMessage(pp::Var(kHaveFocus));
+ } else {
+ PostMessage(pp::Var(kDontHaveFocus));
+ }
+ }
+
+ /// Scrolling the mouse wheel causes a DidChangeView event.
+ void DidChangeView(const pp::Rect& position,
+ const pp::Rect& clip) {
+ PostMessage(pp::Var(kDidChangeView));
+ }
+
+ /// Called by the browser to handle the postMessage() call in Javascript.
+ /// Detects which method is being called from the message contents, and
+ /// calls the appropriate function. Posts the result back to the browser
+ /// asynchronously.
+ /// @param[in] var_message The message posted by the browser. The only
+ /// supported message is |kCancelMessage|. If we receive this, we
+ /// cancel the shared queue.
+ virtual void HandleMessage(const pp::Var& var_message) {
+ std::string message = var_message.AsString();
+ if (kCancelMessage == message) {
+ std::string reply = "Received cancel : only Focus events will be "
+ "displayed. Worker thread for mouse/wheel/keyboard will exit.";
+ PostMessage(pp::Var(reply));
+ printf("Calling cancel queue\n");
+ CancelQueueAndWaitForWorker();
+ }
+ }
+
+ // HandleInputEvent operates on the main Pepper thread. Here we
+ // illustrate copying the Pepper input event to our own custom event type.
+ // Since we need to use Pepper API calls to convert it, we must do the
+ // conversion on the main thread. Once we have converted it to our own
+ // event type, we push that into a thread-safe queue and quickly return.
+ // The worker thread can process the custom event and do whatever
+ // (possibly slow) things it wants to do without making the browser
+ // become unresponsive.
+ // We dynamically allocate a sub-class of our custom event (Event)
+ // so that the queue can contain an Event*.
+ virtual bool HandleInputEvent(const pp::InputEvent& event) {
+ Event* event_ptr = NULL;
+ switch (event.GetType()) {
+ case PP_INPUTEVENT_TYPE_IME_COMPOSITION_START:
+ case PP_INPUTEVENT_TYPE_IME_COMPOSITION_UPDATE:
+ case PP_INPUTEVENT_TYPE_IME_COMPOSITION_END:
+ case PP_INPUTEVENT_TYPE_IME_TEXT:
+ // these cases are not handled...fall through below...
+ case PP_INPUTEVENT_TYPE_UNDEFINED:
+ break;
+ case PP_INPUTEVENT_TYPE_MOUSEDOWN:
+ case PP_INPUTEVENT_TYPE_MOUSEUP:
+ case PP_INPUTEVENT_TYPE_MOUSEMOVE:
+ case PP_INPUTEVENT_TYPE_MOUSEENTER:
+ case PP_INPUTEVENT_TYPE_MOUSELEAVE:
+ {
+ pp::MouseInputEvent mouse_event(event);
+ PP_InputEvent_MouseButton pp_button = mouse_event.GetButton();
+ MouseEvent::MouseButton mouse_button = MouseEvent::kNone;
+ switch (pp_button) {
+ case PP_INPUTEVENT_MOUSEBUTTON_NONE:
+ mouse_button = MouseEvent::kNone;
+ break;
+ case PP_INPUTEVENT_MOUSEBUTTON_LEFT:
+ mouse_button = MouseEvent::kLeft;
+ break;
+ case PP_INPUTEVENT_MOUSEBUTTON_MIDDLE:
+ mouse_button = MouseEvent::kMiddle;
+ break;
+ case PP_INPUTEVENT_MOUSEBUTTON_RIGHT:
+ mouse_button = MouseEvent::kRight;
+ break;
+ }
+ event_ptr = new MouseEvent(
+ ConvertEventModifier(mouse_event.GetModifiers()),
+ mouse_button, mouse_event.GetPosition().x(),
+ mouse_event.GetPosition().y(), mouse_event.GetClickCount(),
+ mouse_event.GetTimeStamp());
+ }
+ break;
+ case PP_INPUTEVENT_TYPE_WHEEL:
+ {
+ pp::WheelInputEvent wheel_event(event);
+ event_ptr = new WheelEvent(
+ ConvertEventModifier(wheel_event.GetModifiers()),
+ wheel_event.GetDelta().x(), wheel_event.GetDelta().y(),
+ wheel_event.GetTicks().x(), wheel_event.GetTicks().y(),
+ wheel_event.GetScrollByPage(), wheel_event.GetTimeStamp());
+ }
+ break;
+ case PP_INPUTEVENT_TYPE_RAWKEYDOWN:
+ case PP_INPUTEVENT_TYPE_KEYDOWN:
+ case PP_INPUTEVENT_TYPE_KEYUP:
+ case PP_INPUTEVENT_TYPE_CHAR:
+ case PP_INPUTEVENT_TYPE_CONTEXTMENU:
+ {
+ pp::KeyboardInputEvent key_event(event);
+ event_ptr = new KeyEvent(
+ ConvertEventModifier(key_event.GetModifiers()),
+ key_event.GetKeyCode(), key_event.GetTimeStamp(),
+ key_event.GetCharacterText().DebugString());
+ }
+ break;
+ default:
+ {
+ // For any unhandled events, send a message to the browser
+ // so that the user is aware of these and can investigate.
+ std::stringstream oss;
+ oss << "Default (unhandled) event, type=" << event.GetType();
+ PostMessage(oss.str());
+ }
+ break;
+ }
+ event_queue_.Push(event_ptr);
+ return true;
+ }
+
+ // Return an event from the thread-safe queue, waiting for a new event
+ // to occur if the queue is empty. Set |was_queue_cancelled| to indicate
+ // whether the queue was cancelled. If it was cancelled, then the
+ // Event* will be NULL.
+ const Event* GetEventFromQueue(bool *was_queue_cancelled) {
+ Event* event;
+ QueueGetResult result = event_queue_.GetItem(&event, kWait);
+ if (result == kQueueWasCancelled) {
+ *was_queue_cancelled = true;
+ return NULL;
+ }
+ *was_queue_cancelled = false;
+ return event;
+ }
+
+ // This method is called from the worker thread using CallOnMainThread.
+ // It is not static, and allows PostMessage to be called.
+ void* PostStringToBrowser(int32_t result, std::string data_to_send) {
+ PostMessage(pp::Var(data_to_send));
+ return 0;
+ }
+
+ // |ProcessEventOnWorkerThread| is a static method that is run
+ // by a thread. It pulls events from the queue, converts
+ // them to a string, and calls CallOnMainThread so that
+ // PostStringToBrowser will be called, which will call PostMessage
+ // to send the converted event back to the browser.
+ static void* ProcessEventOnWorkerThread(void* param) {
+ EventInstance* event_instance = static_cast<EventInstance*>(param);
+ while (1) {
+ // Grab a generic Event* so that down below we can call
+ // event->ToString(), which will use the correct virtual method
+ // to convert the event to a string. This 'conversion' is
+ // the 'work' being done on the worker thread. In an application
+ // the work might involve changing application state based on
+ // the event that was processed.
+ bool queue_cancelled;
+ const Event* event = event_instance->GetEventFromQueue(&queue_cancelled);
+ if (queue_cancelled) {
+ printf("Queue was cancelled, worker thread exiting\n");
+ pthread_exit(NULL);
+ }
+ std::string event_string = event->ToString();
+ delete event;
+ // Need to invoke callback on main thread.
+ pp::Module::Get()->core()->CallOnMainThread(
+ 0,
+ event_instance->callback_factory().NewCallback(
+ &EventInstance::PostStringToBrowser,
+ event_string));
+ } // end of while loop.
+ return 0;
+ }
+
+ // Return the callback factory.
+ // Allows the static method (ProcessEventOnWorkerThread) to use
+ // the |event_instance| pointer to get the factory.
+ pp::CompletionCallbackFactory<EventInstance, ThreadSafeRefCount>&
+ callback_factory() {
+ return callback_factory_;
+ }
+
+ private:
+ // Cancels the queue (which will cause the thread to exit).
+ // Wait for the thread. Set |event_thread_| to NULL so we only
+ // execute the body once.
+ void CancelQueueAndWaitForWorker() {
+ if (event_thread_) {
+ event_queue_.CancelQueue();
+ pthread_join(event_thread_, NULL);
+ event_thread_ = NULL;
+ }
+ }
+ pthread_t event_thread_;
+ LockingQueue<Event*> event_queue_;
+ pp::CompletionCallbackFactory<EventInstance, ThreadSafeRefCount>
+ callback_factory_;
+};
+
+// The EventModule provides an implementation of pp::Module that creates
+// EventInstance objects when invoked. This is part of the glue code that makes
+// our example accessible to ppapi.
+class EventModule : public pp::Module {
+ public:
+ EventModule() : pp::Module() {}
+ virtual ~EventModule() {}
+
+ virtual pp::Instance* CreateInstance(PP_Instance instance) {
+ return new EventInstance(instance);
+ }
+};
+
+} // namespace
+
+// Implement the required pp::CreateModule function that creates our specific
+// kind of Module (in this case, EventModule). This is part of the glue code
+// that makes our example accessible to ppapi.
+namespace pp {
+ Module* CreateModule() {
+ return new event_queue::EventModule();
+ }
+}
+
diff --git a/native_client_sdk/src/examples/multithreaded_input_events/mt_input_events.html b/native_client_sdk/src/examples/multithreaded_input_events/mt_input_events.html
new file mode 100644
index 0000000..7e85e1c
--- /dev/null
+++ b/native_client_sdk/src/examples/multithreaded_input_events/mt_input_events.html
@@ -0,0 +1,80 @@
+<!DOCTYPE html>
+<html>
+ <!--
+ Copyright (c) 2011 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.
+ -->
+<head>
+ <title>Input Events</title>
+
+ <script type="text/javascript">
+ var kMaxArraySize = 20;
+ var messageArray = new Array();
+ var eventModule = null;
+
+ function $(id) {
+ return document.getElementById(id);
+ }
+
+ // Indicate success when the NaCl module has loaded.
+ function moduleDidLoad() {
+ eventModule = document.getElementById('event_module');
+ }
+
+ function receiveMessage(message) {
+ // Show last |kMaxArraySize| events in html.
+ messageArray.push(message.data);
+ if (messageArray.length > kMaxArraySize) {
+ messageArray.shift();
+ }
+ var newData = messageArray.join('<BR>');
+ document.getElementById('eventString').innerHTML = newData;
+ // Print event to console.
+ console.log(message.data);
+ }
+ function cancelQueue() {
+ if (eventModule == null) {
+ console.log('Module is not loaded.');
+ return;
+ }
+ eventModule.postMessage('CANCEL');
+ }
+ </script>
+</head>
+<body>
+<h1>InputEvent Handling Example</h1>
+ <div id="listener">
+ <script type="text/javascript">
+ $('listener').addEventListener('message', receiveMessage, true);
+ $('listener').addEventListener('load', moduleDidLoad, true);
+ </script>
+ <button onclick="cancelQueue()">Kill worker thread and queue</button>
+ <p/>
+ <embed name="nacl_module"
+ id="event_module"
+ width=400 height=400
+ src="mt_input_events.nmf"
+ type="application/x-nacl"
+ style="background-color:gray" />
+ </div>
+<p>
+This example demonstrates handling of input events in PPAPI.</p>
+<p>
+Each time an input event happens in the context of the gray box,
+the main thread in the embedded NaCl module converts it from a Pepper input
+event to a non-Pepper event and puts this custom event onto a shared queue.
+A worker thread in the embedded NaCl module reads events from the queue,
+and converts each event to a string and then uses CallOnMainThread to post a
+message describing the event back to JavaScript, which prints a message to the
+JavaScript console in Chrome and to a string on the page.</p>
+<p>
+If you press the 'Kill worker thread and queue' button, then the main thread
+(which puts events on the queue) will call CancelQueue, indicating that the
+main thread will no longer put events on the queue. When the worker sees that
+the shared queue has been cancelled, the worker thread will terminate.</p>
+<h2>Events</h2>
+<pre><p><b id='eventString'>None</b></p>
+</pre>
+</body>
+</html>
diff --git a/native_client_sdk/src/examples/multithreaded_input_events/shared_queue.h b/native_client_sdk/src/examples/multithreaded_input_events/shared_queue.h
new file mode 100644
index 0000000..22ff7ae
--- /dev/null
+++ b/native_client_sdk/src/examples/multithreaded_input_events/shared_queue.h
@@ -0,0 +1,137 @@
+// Copyright (c) 2011 The Native Client Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SHARED_QUEUE_H
+#define SHARED_QUEUE_H
+
+#include <pthread.h>
+#include <cassert>
+#include <deque>
+
+#include "examples/multithreaded_input_events/thread_safe_ref_count.h"
+
+namespace event_queue {
+
+// This file provides a queue that uses a mutex and condition variable so that
+// one thread can put pointers into the queue and another thread can pull items
+// out of the queue.
+
+const int kPthreadMutexSuccess = 0;
+
+// Specifies whether we want to wait for the queue.
+enum QueueWaitingFlag {
+ kWait = 0,
+ kDontWait
+};
+
+// Indicates if we got an item, did not wait, or if the queue was cancelled.
+enum QueueGetResult {
+ kReturnedItem = 0,
+ kDidNotWait = 1,
+ kQueueWasCancelled
+};
+
+// LockingQueue contains a collection of <T>, such as a collection of
+// objects or pointers. The Push() method is used to add items to the
+// queue in a thread-safe manner. The GetItem() is used to retrieve
+// items from the queue in a thread-safe manner.
+template <class T>
+class LockingQueue {
+ public:
+ LockingQueue() : quit_(false) {
+ int result = pthread_mutex_init(&queue_mutex_, NULL);
+ assert(result == 0);
+ result = pthread_cond_init(&queue_condition_var_, NULL);
+ assert(result == 0);
+ }
+ ~LockingQueue() {
+ pthread_mutex_destroy(&queue_mutex_);
+ }
+
+ // The producer (who instantiates the queue) calls this to tell the
+ // consumer that the queue is no longer being used.
+ void CancelQueue() {
+ ScopedLock scoped_mutex(&queue_mutex_);
+ quit_ = true;
+ // Signal the condition var so that if a thread is waiting in
+ // GetItem the thread will wake up and see that the queue has
+ // been cancelled.
+ pthread_cond_signal(&queue_condition_var_);
+ }
+
+ // The consumer calls this to see if the queue has been cancelled by
+ // the producer. If so, the thread should not call GetItem and may
+ // need to terminate -- i.e. in a case where the producer created
+ // the consumer thread.
+ bool IsCancelled() {
+ ScopedLock scoped_mutex(&queue_mutex_);
+ return quit_;
+ }
+
+ // Grabs the mutex and pushes a new item to the end of the queue if the
+ // queue is not full. Signals the condition variable so that a thread
+ // that is waiting will wake up and grab the item.
+ void Push(const T& item) {
+ ScopedLock scoped_mutex(&queue_mutex_);
+ the_queue_.push_back(item);
+ pthread_cond_signal(&queue_condition_var_);
+ }
+
+ // Tries to pop the front element from the queue; returns an enum:
+ // kReturnedItem if an item is returned in |item_ptr|,
+ // kDidNotWait if |wait| was kDontWait and the queue was empty,
+ // kQueueWasCancelled if the producer called CancelQueue().
+ // If |wait| is kWait, GetItem will wait to return until the queue
+ // contains an item (unless the queue is cancelled).
+ QueueGetResult GetItem(T* item_ptr, QueueWaitingFlag wait) {
+ // Because we use both pthread_mutex_lock and pthread_cond_wait,
+ // we directly use the mutex instead of using ScopedLock.
+ ScopedLock scoped_mutex(&queue_mutex_);
+ // Use a while loop to get an item. If the user does not want to wait,
+ // we will exit from the loop anyway, unlocking the mutex.
+ // If the user does want to wait, we will wait for pthread_cond_wait,
+ // and the while loop will check is_empty_no_locking() one more
+ // time so that a spurious wake-up of pthread_cond_wait is handled.
+ // If |quit_| has been set, break out of the loop.
+ while (!quit_ && is_empty_no_locking()) {
+ // If user doesn't want to wait, return...
+ if (kDontWait == wait) {
+ return kDidNotWait;
+ }
+ // Wait for signal to occur.
+ pthread_cond_wait(&queue_condition_var_, &queue_mutex_);
+ }
+ // Check to see if quit_ woke us up
+ if (quit_) {
+ return kQueueWasCancelled;
+ }
+
+ // At this point, the queue was either not empty or, if it was empty,
+ // we called pthread_cond_wait (which released the mutex, waited for the
+ // signal to occur, and then atomically reacquired the mutex).
+ // Thus, if we are here, the queue cannot be empty because we either
+ // had the mutex and verified it was not empty, or we waited for the
+ // producer to put an item in and signal a single thread (us).
+ T& item = the_queue_.front();
+ *item_ptr = item;
+ the_queue_.pop_front();
+ return kReturnedItem;
+ }
+
+ private:
+ std::deque<T> the_queue_;
+ bool quit_;
+ pthread_mutex_t queue_mutex_;
+ pthread_cond_t queue_condition_var_;
+
+ // This is used by methods that already have the lock.
+ bool is_empty_no_locking() const {
+ return the_queue_.empty();
+ }
+};
+
+} // end of unnamed namespace
+
+#endif // SHARED_QUEUE_H
+
diff --git a/native_client_sdk/src/examples/multithreaded_input_events/thread_safe_ref_count.h b/native_client_sdk/src/examples/multithreaded_input_events/thread_safe_ref_count.h
new file mode 100644
index 0000000..f4e6beb
--- /dev/null
+++ b/native_client_sdk/src/examples/multithreaded_input_events/thread_safe_ref_count.h
@@ -0,0 +1,63 @@
+// Copyright (c) 2011 The Native Client Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THREAD_SAFE_REF_COUNT_H
+#define THREAD_SAFE_REF_COUNT_H
+
+#include <pthread.h>
+#include <cassert>
+
+const int kPthreadMutexSuccess = 0;
+
+namespace event_queue {
+// Some synchronization helper classes.
+
+class ScopedLock {
+ public:
+ explicit ScopedLock(pthread_mutex_t* mutex)
+ : mutex_(mutex) {
+ if (pthread_mutex_lock(mutex_) != kPthreadMutexSuccess) {
+ mutex_ = NULL;
+ }
+ }
+ ~ScopedLock() {
+ if (mutex_ != NULL) {
+ pthread_mutex_unlock(mutex_);
+ }
+ }
+ private:
+ ScopedLock& operator=(const ScopedLock&); // Not implemented, do not use.
+ ScopedLock(const ScopedLock&); // Not implemented, do not use.
+ pthread_mutex_t* mutex_; // Weak reference, passed in to constructor.
+};
+
+class ThreadSafeRefCount {
+ public:
+ ThreadSafeRefCount()
+ : ref_(0) {
+ Init();
+ }
+
+ void Init() {
+ pthread_mutex_init(&mutex_, NULL);
+ }
+
+ int32_t AddRef() {
+ ScopedLock s(&mutex_);
+ return ++ref_;
+ }
+
+ int32_t Release() {
+ ScopedLock s(&mutex_);
+ return --ref_;
+ }
+
+ private:
+ int32_t ref_;
+ pthread_mutex_t mutex_;
+};
+
+} // namespace
+#endif // THREAD_SAFE_REF_COUNT_H
+
diff --git a/native_client_sdk/src/examples/pi_generator/build.scons b/native_client_sdk/src/examples/pi_generator/build.scons
new file mode 100644
index 0000000..8148242
--- /dev/null
+++ b/native_client_sdk/src/examples/pi_generator/build.scons
@@ -0,0 +1,40 @@
+#! -*- python -*-
+#
+# Copyright (c) 2011 The Native Client Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import make_nacl_env
+import os
+
+nacl_env = make_nacl_env.NaClEnvironment(
+ use_c_plus_plus_libs=True, nacl_platform=os.getenv('NACL_TARGET_PLATFORM'),
+ install_subdir='pi_generator', lib_prefix='..')
+nacl_env.Append(
+ # Add a CPPPATH that enables the full-path #include directives, such as
+ # #include "examples/sine_synth/sine_synth.h"
+ CPPPATH=[os.path.dirname(os.path.dirname(os.getcwd()))],
+ # Strict ANSI compliance.
+ EXTRA_CCFLAGS=['-pedantic'],
+ )
+
+sources = ['pi_generator.cc', 'pi_generator_module.cc']
+
+opt_nexes, dbg_nexes = nacl_env.AllNaClModules(sources, 'pi_generator')
+
+# This target is used by the SDK build system to provide a prebuilt version
+# of the example in the SDK installer.
+nacl_env.InstallPrebuilt('pi_generator')
+
+app_files = [
+ 'pi_generator.html',
+ 'pi_generator.nmf',
+ ]
+
+# Split the install of the .nexes from the other app sources so that the strip
+# action is applied to the .nexes only.
+install_nexes = nacl_env.NaClStrippedInstall(dir=nacl_env['NACL_INSTALL_ROOT'],
+ source=opt_nexes)
+install_app = nacl_env.Install(dir=nacl_env['NACL_INSTALL_ROOT'],
+ source=app_files)
+nacl_env.Alias('install', source=install_app + install_nexes)
diff --git a/native_client_sdk/src/examples/pi_generator/pi_generator.cc b/native_client_sdk/src/examples/pi_generator/pi_generator.cc
new file mode 100644
index 0000000..f5e6e4d
--- /dev/null
+++ b/native_client_sdk/src/examples/pi_generator/pi_generator.cc
@@ -0,0 +1,240 @@
+// Copyright (c) 2011 The Native Client 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 "examples/pi_generator/pi_generator.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <cassert>
+#include <cmath>
+#include <cstring>
+#include <string>
+#include "ppapi/cpp/completion_callback.h"
+#include "ppapi/cpp/var.h"
+
+namespace {
+const int kPthreadMutexSuccess = 0;
+const char* const kPaintMethodId = "paint";
+const double kInvalidPiValue = -1.0;
+const int kMaxPointCount = 1000000000; // The total number of points to draw.
+const uint32_t kOpaqueColorMask = 0xff000000; // Opaque pixels.
+const uint32_t kRedMask = 0xff0000;
+const uint32_t kBlueMask = 0xff;
+const uint32_t kRedShift = 16;
+const uint32_t kBlueShift = 0;
+
+// This is called by the browser when the 2D context has been flushed to the
+// browser window.
+void FlushCallback(void* data, int32_t result) {
+ static_cast<pi_generator::PiGenerator*>(data)->set_flush_pending(false);
+}
+} // namespace
+
+namespace pi_generator {
+
+// A small helper RAII class that implementes a scoped pthread_mutex lock.
+class ScopedMutexLock {
+ public:
+ explicit ScopedMutexLock(pthread_mutex_t* mutex) : mutex_(mutex) {
+ if (pthread_mutex_lock(mutex_) != kPthreadMutexSuccess) {
+ mutex_ = NULL;
+ }
+ }
+ ~ScopedMutexLock() {
+ if (mutex_)
+ pthread_mutex_unlock(mutex_);
+ }
+ bool is_valid() const {
+ return mutex_ != NULL;
+ }
+ private:
+ pthread_mutex_t* mutex_; // Weak reference.
+};
+
+// A small helper RAII class used to acquire and release the pixel lock.
+class ScopedPixelLock {
+ public:
+ explicit ScopedPixelLock(PiGenerator* image_owner)
+ : image_owner_(image_owner), pixels_(image_owner->LockPixels()) {}
+
+ ~ScopedPixelLock() {
+ pixels_ = NULL;
+ image_owner_->UnlockPixels();
+ }
+
+ uint32_t* pixels() const {
+ return pixels_;
+ }
+ private:
+ PiGenerator* image_owner_; // Weak reference.
+ uint32_t* pixels_; // Weak reference.
+
+ ScopedPixelLock(); // Not implemented, do not use.
+};
+
+PiGenerator::PiGenerator(PP_Instance instance)
+ : pp::Instance(instance),
+ graphics_2d_context_(NULL),
+ pixel_buffer_(NULL),
+ flush_pending_(false),
+ quit_(false),
+ compute_pi_thread_(0),
+ pi_(0.0) {
+ pthread_mutex_init(&pixel_buffer_mutex_, NULL);
+}
+
+PiGenerator::~PiGenerator() {
+ quit_ = true;
+ if (compute_pi_thread_) {
+ pthread_join(compute_pi_thread_, NULL);
+ }
+ DestroyContext();
+ // The ComputePi() thread should be gone by now, so there is no need to
+ // acquire the mutex for |pixel_buffer_|.
+ delete pixel_buffer_;
+ pthread_mutex_destroy(&pixel_buffer_mutex_);
+}
+
+void PiGenerator::DidChangeView(const pp::Rect& position,
+ const pp::Rect& clip) {
+ if (position.size().width() == width() &&
+ position.size().height() == height())
+ return; // Size didn't change, no need to update anything.
+
+ // Create a new device context with the new size.
+ DestroyContext();
+ CreateContext(position.size());
+ // Delete the old pixel buffer and create a new one.
+ ScopedMutexLock scoped_mutex(&pixel_buffer_mutex_);
+ delete pixel_buffer_;
+ pixel_buffer_ = NULL;
+ if (graphics_2d_context_ != NULL) {
+ pixel_buffer_ = new pp::ImageData(this,
+ PP_IMAGEDATAFORMAT_BGRA_PREMUL,
+ graphics_2d_context_->size(),
+ false);
+ }
+}
+
+bool PiGenerator::Init(uint32_t argc, const char* argn[], const char* argv[]) {
+ pthread_create(&compute_pi_thread_, NULL, ComputePi, this);
+ return true;
+}
+
+uint32_t* PiGenerator::LockPixels() {
+ void* pixels = NULL;
+ // Do not use a ScopedMutexLock here, since the lock needs to be held until
+ // the matching UnlockPixels() call.
+ if (pthread_mutex_lock(&pixel_buffer_mutex_) == kPthreadMutexSuccess) {
+ if (pixel_buffer_ != NULL && !pixel_buffer_->is_null()) {
+ pixels = pixel_buffer_->data();
+ }
+ }
+ return reinterpret_cast<uint32_t*>(pixels);
+}
+
+void PiGenerator::HandleMessage(const pp::Var& var_message) {
+ if (!var_message.is_string()) {
+ PostMessage(pp::Var(kInvalidPiValue));
+ }
+ std::string message = var_message.AsString();
+ if (message == kPaintMethodId) {
+ Paint();
+ } else {
+ PostMessage(pp::Var(kInvalidPiValue));
+ }
+}
+
+void PiGenerator::UnlockPixels() const {
+ pthread_mutex_unlock(&pixel_buffer_mutex_);
+}
+
+void PiGenerator::Paint() {
+ ScopedMutexLock scoped_mutex(&pixel_buffer_mutex_);
+ if (!scoped_mutex.is_valid()) {
+ return;
+ }
+ FlushPixelBuffer();
+ // Post the current estimate of Pi back to the browser.
+ pp::Var pi_estimate(pi());
+ // Paint() is called on the main thread, so no need for CallOnMainThread()
+ // here. It's OK to just post the message.
+ PostMessage(pi_estimate);
+}
+
+void PiGenerator::CreateContext(const pp::Size& size) {
+ ScopedMutexLock scoped_mutex(&pixel_buffer_mutex_);
+ if (!scoped_mutex.is_valid()) {
+ return;
+ }
+ if (IsContextValid())
+ return;
+ graphics_2d_context_ = new pp::Graphics2D(this, size, false);
+ if (!BindGraphics(*graphics_2d_context_)) {
+ printf("Couldn't bind the device context\n");
+ }
+}
+
+void PiGenerator::DestroyContext() {
+ ScopedMutexLock scoped_mutex(&pixel_buffer_mutex_);
+ if (!scoped_mutex.is_valid()) {
+ return;
+ }
+ if (!IsContextValid())
+ return;
+ delete graphics_2d_context_;
+ graphics_2d_context_ = NULL;
+}
+
+void PiGenerator::FlushPixelBuffer() {
+ if (!IsContextValid())
+ return;
+ // Note that the pixel lock is held while the buffer is copied into the
+ // device context and then flushed.
+ graphics_2d_context_->PaintImageData(*pixel_buffer_, pp::Point());
+ if (flush_pending())
+ return;
+ set_flush_pending(true);
+ graphics_2d_context_->Flush(pp::CompletionCallback(&FlushCallback, this));
+}
+
+void* PiGenerator::ComputePi(void* param) {
+ int count = 0; // The number of points put inside the inscribed quadrant.
+ unsigned int seed = 1;
+ PiGenerator* pi_generator = static_cast<PiGenerator*>(param);
+ srand(seed);
+ for (int i = 1; i <= kMaxPointCount && !pi_generator->quit(); ++i) {
+ ScopedPixelLock scoped_pixel_lock(pi_generator);
+ uint32_t* pixel_bits = scoped_pixel_lock.pixels();
+ if (pixel_bits == NULL) {
+ // Note that if the pixel buffer never gets initialized, this won't ever
+ // paint anything. Which is probably the right thing to do. Also, this
+ // clause means that the image will not get the very first few Pi dots,
+ // since it's possible that this thread starts before the pixel buffer is
+ // initialized.
+ continue;
+ }
+ double x = static_cast<double>(rand_r(&seed)) / RAND_MAX;
+ double y = static_cast<double>(rand_r(&seed)) / RAND_MAX;
+ double distance = sqrt(x * x + y * y);
+ int px = x * pi_generator->width();
+ int py = (1.0 - y) * pi_generator->height();
+ uint32_t color = pixel_bits[pi_generator->width() * py + px];
+ if (distance < 1.0) {
+ // Set color to blue.
+ ++count;
+ pi_generator->pi_ = 4.0 * count / i;
+ color += 4 << kBlueShift;
+ color &= kBlueMask;
+ } else {
+ // Set color to red.
+ color += 4 << kRedShift;
+ color &= kRedMask;
+ }
+ pixel_bits[pi_generator->width() * py + px] = color | kOpaqueColorMask;
+ }
+ return 0;
+}
+
+} // namespace pi_generator
diff --git a/native_client_sdk/src/examples/pi_generator/pi_generator.h b/native_client_sdk/src/examples/pi_generator/pi_generator.h
new file mode 100644
index 0000000..beecf41
--- /dev/null
+++ b/native_client_sdk/src/examples/pi_generator/pi_generator.h
@@ -0,0 +1,117 @@
+// Copyright (c) 2011 The Native Client Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXAMPLES_PI_GENERATOR_PI_GENERATOR_H_
+#define EXAMPLES_PI_GENERATOR_PI_GENERATOR_H_
+
+#include <pthread.h>
+#include <map>
+#include <vector>
+#include "ppapi/cpp/graphics_2d.h"
+#include "ppapi/cpp/image_data.h"
+#include "ppapi/cpp/instance.h"
+#include "ppapi/cpp/rect.h"
+#include "ppapi/cpp/size.h"
+
+namespace pi_generator {
+
+// The Instance class. One of these exists for each instance of your NaCl
+// module on the web page. The browser will ask the Module object to create
+// a new Instance for each occurrence of the <embed> tag that has these
+// attributes:
+// type="application/x-nacl"
+// nacl="pi_generator.nmf"
+class PiGenerator : public pp::Instance {
+ public:
+ explicit PiGenerator(PP_Instance instance);
+ virtual ~PiGenerator();
+
+ // Start up the ComputePi() thread.
+ virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]);
+
+ // Update the graphics context to the new size, and regenerate |pixel_buffer_|
+ // to fit the new size as well.
+ virtual void DidChangeView(const pp::Rect& position, const pp::Rect& clip);
+
+ // Called by the browser to handle the postMessage() call in Javascript.
+ // The message in this case is expected to contain the string 'paint', and
+ // if so this invokes the Paint() function. If |var_message| is not a string
+ // type, or contains something other than 'paint', this method posts an
+ // invalid value for Pi (-1.0) back to the browser.
+ virtual void HandleMessage(const pp::Var& var_message);
+
+ // Return a pointer to the pixels represented by |pixel_buffer_|. When this
+ // method returns, the underlying |pixel_buffer_| object is locked. This
+ // call must have a matching UnlockPixels() or various threading errors
+ // (e.g. deadlock) will occur.
+ uint32_t* LockPixels();
+ // Release the image lock acquired by LockPixels().
+ void UnlockPixels() const;
+
+ // Flushes its contents of |pixel_buffer_| to the 2D graphics context. The
+ // ComputePi() thread fills in |pixel_buffer_| pixels as it computes Pi.
+ // This method is called by HandleMessage when a message containing 'paint'
+ // is received. Echos the current value of pi as computed by the Monte Carlo
+ // method by posting the value back to the browser.
+ void Paint();
+
+ bool quit() const {
+ return quit_;
+ }
+
+ // |pi_| is computed in the ComputePi() thread.
+ double pi() const {
+ return pi_;
+ }
+
+ int width() const {
+ return pixel_buffer_ ? pixel_buffer_->size().width() : 0;
+ }
+ int height() const {
+ return pixel_buffer_ ? pixel_buffer_->size().height() : 0;
+ }
+
+ // Indicate whether a flush is pending. This can only be called from the
+ // main thread; it is not thread safe.
+ bool flush_pending() const {
+ return flush_pending_;
+ }
+ void set_flush_pending(bool flag) {
+ flush_pending_ = flag;
+ }
+
+ private:
+ // Create and initialize the 2D context used for drawing.
+ void CreateContext(const pp::Size& size);
+ // Destroy the 2D drawing context.
+ void DestroyContext();
+ // Push the pixels to the browser, then attempt to flush the 2D context. If
+ // there is a pending flush on the 2D context, then update the pixels only
+ // and do not flush.
+ void FlushPixelBuffer();
+
+ bool IsContextValid() const {
+ return graphics_2d_context_ != NULL;
+ }
+
+ mutable pthread_mutex_t pixel_buffer_mutex_;
+ pp::Graphics2D* graphics_2d_context_;
+ pp::ImageData* pixel_buffer_;
+ bool flush_pending_;
+ bool quit_;
+ pthread_t compute_pi_thread_;
+ double pi_;
+
+ // ComputePi() estimates Pi using Monte Carlo method and it is executed by a
+ // separate thread created in SetWindow(). ComputePi() puts kMaxPointCount
+ // points inside the square whose length of each side is 1.0, and calculates
+ // the ratio of the number of points put inside the inscribed quadrant divided
+ // by the total number of random points to get Pi/4.
+ static void* ComputePi(void* param);
+};
+
+} // namespace pi_generator
+
+#endif // EXAMPLES_PI_GENERATOR_PI_GENERATOR_H_
+
diff --git a/native_client_sdk/src/examples/pi_generator/pi_generator.html b/native_client_sdk/src/examples/pi_generator/pi_generator.html
new file mode 100644
index 0000000..f63e73d
--- /dev/null
+++ b/native_client_sdk/src/examples/pi_generator/pi_generator.html
@@ -0,0 +1,83 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+ <!--
+ Copyright (c) 2011 The Native Client Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style license that can be
+ found in the LICENSE file.
+ -->
+ <head>
+ <title>Monte Carlo Estimate for Pi</title>
+ <script type="text/javascript">
+ var piGenerator = null;
+ var paintInterval = null;
+
+ // Start up the paint timer when the NaCl module has loaded.
+ function moduleDidLoad() {
+ piGenerator = document.getElementById('piGenerator');
+ paintInterval = setInterval('piGenerator.postMessage("paint")', 5);
+ }
+
+ // Handle a message coming from the NaCl module. The message payload is
+ // assumed to contain the current estimated value of Pi. Update the Pi
+ // text display with this value.
+ function handleMessage(message_event) {
+ document.form.pi.value = message_event.data;
+ }
+
+ function pageDidUnload() {
+ clearInterval(paintInterval);
+ }
+ </script>
+ </head>
+ <body id="bodyId" onunload="pageDidUnload()">
+ <h1>Monte Carlo Estimate for Pi</h1>
+ <p>
+ The Native Client module executed in this page creates a thread
+ that estimates pi (&pi;) using the Monte Carlo method.
+ The thread randomly puts 1,000,000,000 points
+ inside a square that shares two sides with a quarter circle (a quadrant).
+ Because the area of
+ the quadrant is r&#178;&pi;/4
+ and the area of
+ the square is r&#178;,
+ dividing the number of points inside the quadrant
+ by the number of points inside the square gives us
+ an estimate of &pi;/4.
+ The textbox under the square
+ shows the current estimate of &pi;.
+ </p>
+ <!-- Load the published .nexe. This includes the 'src' attribute which
+ shows how to load multi-architecture modules. Each entry in the "nexes"
+ object in the .nmf manifest file is a key-value pair: the key is the
+ runtime ('x86-32', 'x86-64', etc.); the value is a URL for the desired NaCl
+ module. To load the debug versions of your .nexes, set the 'src'
+ attribute to the _dbg.nmf version of the manifest file.
+
+ Note: The <EMBED> element is wrapped inside a <DIV>, which has both a 'load'
+ and a 'message' event listener attached. This wrapping method is used
+ instead of attaching the event listeners directly to the <EMBED> element to
+ ensure that the listeners are active before the NaCl module 'load' event
+ fires. This also allows you to use PPB_Messaging.PostMessage() (in C) or
+ pp::Instance.PostMessage() (in C++) from within the initialization code in
+ your NaCl module.
+ -->
+ <div id="listener">
+ <script type="text/javascript">
+ var listener = document.getElementById('listener')
+ listener.addEventListener('load', moduleDidLoad, true);
+ listener.addEventListener('message', handleMessage, true);
+ </script>
+
+ <embed name="nacl_module"
+ id="piGenerator"
+ width=200 height=200
+ src="pi_generator.nmf"
+ type="application/x-nacl" />
+ </div>
+ <br />
+ <form name="form">
+ <input type="text" size="15" name="pi" />
+ </form>
+ </body>
+</html>
diff --git a/native_client_sdk/src/examples/pi_generator/pi_generator_dbg.html b/native_client_sdk/src/examples/pi_generator/pi_generator_dbg.html
new file mode 100644
index 0000000..3308b15
--- /dev/null
+++ b/native_client_sdk/src/examples/pi_generator/pi_generator_dbg.html
@@ -0,0 +1,83 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+ <!--
+ Copyright (c) 2011 The Native Client Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style license that can be
+ found in the LICENSE file.
+ -->
+ <head>
+ <title>Monte Carlo Estimate for Pi</title>
+ <script type="text/javascript">
+ var piGenerator = null;
+ var paintInterval = null;
+
+ // Start up the paint timer when the NaCl module has loaded.
+ function moduleDidLoad() {
+ piGenerator = document.getElementById('piGenerator');
+ paintInterval = setInterval('piGenerator.postMessage("paint")', 5);
+ }
+
+ // Handle a message coming from the NaCl module. The message payload is
+ // assumed to contain the current estimated value of Pi. Update the Pi
+ // text display with this value.
+ function handleMessage(message_event) {
+ document.form.pi.value = message_event.data;
+ }
+
+ function pageDidUnload() {
+ clearInterval(paintInterval);
+ }
+ </script>
+ </head>
+ <body id="bodyId" onunload="pageDidUnload()">
+ <h1>Monte Carlo Estimate for Pi</h1>
+ <p>
+ The Native Client module executed in this page creates a thread
+ that estimates pi (&pi;) using the Monte Carlo method.
+ The thread randomly puts 1,000,000,000 points
+ inside a square that shares two sides with a quarter circle (a quadrant).
+ Because the area of
+ the quadrant is r&#178;&pi;/4
+ and the area of
+ the square is r&#178;,
+ dividing the number of points inside the quadrant
+ by the number of points inside the square gives us
+ an estimate of &pi;/4.
+ The textbox under the square
+ shows the current estimate of &pi;.
+ </p>
+ <!-- Load the published .nexe. This includes the 'src' attribute which
+ shows how to load multi-architecture modules. Each entry in the "nexes"
+ object in the .nmf manifest file is a key-value pair: the key is the
+ runtime ('x86-32', 'x86-64', etc.); the value is a URL for the desired NaCl
+ module. To load the debug versions of your .nexes, set the 'src'
+ attribute to the _dbg.nmf version of the manifest file.
+
+ Note: The <EMBED> element is wrapped inside a <DIV>, which has both a 'load'
+ and a 'message' event listener attached. This wrapping method is used
+ instead of attaching the event listeners directly to the <EMBED> element to
+ ensure that the listeners are active before the NaCl module 'load' event
+ fires. This also allows you to use PPB_Messaging.PostMessage() (in C) or
+ pp::Instance.PostMessage() (in C++) from within the initialization code in
+ your NaCl module.
+ -->
+ <div id="listener">
+ <script type="text/javascript">
+ var listener = document.getElementById('listener')
+ listener.addEventListener('load', moduleDidLoad, true);
+ listener.addEventListener('message', handleMessage, true);
+ </script>
+
+ <embed name="nacl_module"
+ id="piGenerator"
+ width=200 height=200
+ src="pi_generator_dbg.nmf"
+ type="application/x-nacl" />
+ </div>
+ <br />
+ <form name="form">
+ <input type="text" size="15" name="pi" />
+ </form>
+ </body>
+</html>
diff --git a/native_client_sdk/src/examples/pi_generator/pi_generator_module.cc b/native_client_sdk/src/examples/pi_generator/pi_generator_module.cc
new file mode 100644
index 0000000..8477fbf
--- /dev/null
+++ b/native_client_sdk/src/examples/pi_generator/pi_generator_module.cc
@@ -0,0 +1,34 @@
+// Copyright (c) 2011 The Native Client 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 <ppapi/cpp/module.h>
+
+#include "examples/pi_generator/pi_generator.h"
+
+namespace pi_generator {
+// The Module class. The browser calls the CreateInstance() method to create
+// an instance of your NaCl module on the web page. The browser creates a new
+// instance for each <embed> tag with type="application/x-nacl".
+class PiGeneratorModule : public pp::Module {
+ public:
+ PiGeneratorModule() : pp::Module() {}
+ virtual ~PiGeneratorModule() {}
+
+ // Create and return a PiGeneratorInstance object.
+ virtual pp::Instance* CreateInstance(PP_Instance instance) {
+ return new PiGenerator(instance);
+ }
+};
+} // namespace pi_generator
+
+// Factory function called by the browser when the module is first loaded.
+// The browser keeps a singleton of this module. It calls the
+// CreateInstance() method on the object you return to make instances. There
+// is one instance per <embed> tag on the page. This is the main binding
+// point for your NaCl module with the browser.
+namespace pp {
+Module* CreateModule() {
+ return new pi_generator::PiGeneratorModule();
+}
+} // namespace pp
diff --git a/native_client_sdk/src/examples/pong/build.scons b/native_client_sdk/src/examples/pong/build.scons
new file mode 100644
index 0000000..a438bb0
--- /dev/null
+++ b/native_client_sdk/src/examples/pong/build.scons
@@ -0,0 +1,40 @@
+#! -*- python -*-
+#
+# Copyright (c) 2011 The Native Client Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import make_nacl_env
+import os
+
+nacl_env = make_nacl_env.NaClEnvironment(
+ use_c_plus_plus_libs=True, nacl_platform=os.getenv('NACL_TARGET_PLATFORM'),
+ install_subdir='pong', lib_prefix='..')
+nacl_env.Append(
+ # Add a CPPPATH that enables the full-path #include directives, such as
+ # #include "examples/sine_synth/sine_synth.h"
+ CPPPATH=[os.path.dirname(os.path.dirname(os.getcwd()))],
+ # Strict ANSI compliance.
+ EXTRA_CCFLAGS=['-pedantic'],
+ )
+
+sources = ['pong.cc', 'pong_module.cc', 'view.cc']
+
+opt_nexes, dbg_nexes = nacl_env.AllNaClModules(sources, 'pong')
+
+# This target is used by the SDK build system to provide a prebuilt version
+# of the example in the SDK installer.
+nacl_env.InstallPrebuilt('pong')
+
+app_files = [
+ 'pong.html',
+ 'pong.nmf',
+ ]
+
+# Split the install of the .nexes from the other app sources so that the strip
+# action is applied to the .nexes only.
+install_nexes = nacl_env.NaClStrippedInstall(dir=nacl_env['NACL_INSTALL_ROOT'],
+ source=opt_nexes)
+install_app = nacl_env.Install(dir=nacl_env['NACL_INSTALL_ROOT'],
+ source=app_files)
+nacl_env.Alias('install', source=install_app + install_nexes)
diff --git a/native_client_sdk/src/examples/pong/pong.cc b/native_client_sdk/src/examples/pong/pong.cc
new file mode 100644
index 0000000..75ffc7c
--- /dev/null
+++ b/native_client_sdk/src/examples/pong/pong.cc
@@ -0,0 +1,414 @@
+// Copyright (c) 2011 The Native Client 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 "examples/pong/pong.h"
+
+#include <stdio.h>
+#include <cmath>
+#include <string>
+#include "examples/pong/view.h"
+#include "ppapi/c/pp_file_info.h"
+#include "ppapi/c/ppb_file_io.h"
+#include "ppapi/cpp/completion_callback.h"
+#include "ppapi/cpp/file_io.h"
+#include "ppapi/cpp/file_ref.h"
+#include "ppapi/cpp/file_system.h"
+#include "ppapi/cpp/input_event.h"
+#include "ppapi/cpp/rect.h"
+#include "ppapi/cpp/var.h"
+
+namespace {
+
+const uint32_t kSpaceBar = 0x20;
+const uint32_t kUpArrow = 0x26;
+const uint32_t kDownArrow = 0x28;
+
+const int32_t kMaxPointsAllowed = 256;
+const std::string kResetScoreMethodId = "resetScore";
+const uint32_t kUpdateDistance = 4;
+const int32_t kUpdateInterval = 17; // milliseconds
+
+} // namespace
+
+namespace pong {
+
+// Callbacks that are called asynchronously by the system as a result of various
+// pp::FileIO methods.
+namespace AsyncCallbacks {
+// Callback that is called as a result of pp::FileIO::Flush
+void FlushCallback(void*, int32_t) {
+}
+
+// Callback that is called as a result of pp::FileIO::Write
+void WriteCallback(void* data, int32_t bytes_written) {
+ if (bytes_written < 0)
+ return; // error
+ Pong* pong = static_cast<Pong*>(data);
+ pong->offset_ += bytes_written;
+ if (pong->offset_ == pong->bytes_buffer_.length()) {
+ pong->file_io_->Flush(pp::CompletionCallback(FlushCallback, NULL));
+ } else {
+ // Not all the bytes to be written have been written, so call
+ // pp::FileIO::Write again.
+ pong->file_io_->Write(pong->offset_, &pong->bytes_buffer_[pong->offset_],
+ pong->bytes_buffer_.length() - pong->offset_,
+ pp::CompletionCallback(WriteCallback, pong));
+ }
+}
+
+// Callback that is called as a result of pp::FileSystem::Open
+void FileSystemOpenCallback(void* data, int32_t result) {
+ if (result != PP_OK)
+ return;
+ Pong* pong = static_cast<Pong*>(data);
+ pong->UpdateScoreFromFile();
+}
+
+// Callback that is called as a result of pp::FileIO::Read
+void ReadCallback(void* data, int32_t bytes_read) {
+ if (bytes_read < 0)
+ return; // error
+ Pong* pong = static_cast<Pong*>(data);
+ pong->bytes_to_read_ -= bytes_read;
+ if (pong->bytes_to_read_ == 0) {
+ // File has been read to completion. Parse the bytes to get the scores.
+ pong->UpdateScoreFromBuffer();
+ } else {
+ pong->offset_ += bytes_read;
+ pong->file_io_->Read(pong->offset_,
+ &pong->bytes_buffer_[pong->offset_],
+ pong->bytes_to_read_,
+ pp::CompletionCallback(ReadCallback, pong));
+ }
+}
+
+// Callback that is called as a result of pp::FileIO::Query
+void QueryCallback(void* data, int32_t result) {
+ if (result != PP_OK)
+ return;
+ Pong* pong = static_cast<Pong*>(data);
+ pong->bytes_to_read_ = pong->file_info_.size;
+ pong->offset_ = 0;
+ pong->bytes_buffer_.resize(pong->bytes_to_read_);
+ pong->file_io_->Read(pong->offset_,
+ &pong->bytes_buffer_[0],
+ pong->bytes_to_read_,
+ pp::CompletionCallback(ReadCallback, pong));
+}
+
+// Callback that is called as a result of pp::FileIO::Open
+void FileOpenCallback(void*data, int32_t result) {
+ if (result != PP_OK) {
+ return;
+ }
+ Pong* pong = static_cast<Pong*>(data);
+ // Query the file in order to get the file size.
+ pong->file_io_->Query(&pong->file_info_, pp::CompletionCallback(QueryCallback,
+ pong));
+}
+
+// Callback that is called as a result of pp::Core::CallOnMainThread
+void UpdateCallback(void* data, int32_t /*result*/) {
+ Pong* pong = static_cast<Pong*>(data);
+ pong->Update();
+}
+
+} // namespace AsyncCallbacks
+
+class UpdateScheduler {
+ public:
+ UpdateScheduler(int32_t delay, Pong* pong)
+ : delay_(delay), pong_(pong) {}
+ ~UpdateScheduler() {
+ pp::Core* core = pp::Module::Get()->core();
+ core->CallOnMainThread(delay_, pp::CompletionCallback(
+ AsyncCallbacks::UpdateCallback, pong_));
+ }
+
+ private:
+ int32_t delay_; // milliseconds
+ Pong* pong_; // weak
+};
+
+Pong::Pong(PP_Instance instance)
+ : pp::Instance(instance),
+ bytes_to_read_(0),
+ offset_(0),
+ file_io_(NULL),
+ file_ref_(NULL),
+ file_system_(NULL),
+ view_(NULL),
+ delta_x_(0),
+ delta_y_(0),
+ player_score_(0),
+ computer_score_(0) {
+ // Request to receive input events.
+ RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE | PP_INPUTEVENT_CLASS_KEYBOARD);
+}
+
+Pong::~Pong() {
+ delete view_;
+ file_io_->Close();
+ delete file_io_;
+ delete file_ref_;
+ delete file_system_;
+}
+
+bool Pong::Init(uint32_t argc, const char* argn[], const char* argv[]) {
+ view_ = new View(this);
+ // Read the score from file.
+ file_system_ = new pp::FileSystem(this, PP_FILESYSTEMTYPE_LOCALPERSISTENT);
+ file_ref_ = new pp::FileRef(*file_system_, "/pong_score");
+ // We kick off a series of callbacks which open a file, query the file for
+ // it's length in bytes, read the file contents, and then update the score
+ // display based on the file contents.
+ int32_t rv = file_system_->Open(
+ 1024, pp::CompletionCallback(AsyncCallbacks::FileSystemOpenCallback,
+ this));
+ if (rv != PP_OK_COMPLETIONPENDING) {
+ PostMessage(pp::Var("ERROR: Could not open local persistent file system."));
+ return true;
+ }
+ UpdateScoreDisplay();
+ UpdateScheduler(kUpdateInterval, this);
+ return true;
+}
+
+void Pong::DidChangeView(const pp::Rect& position,
+ const pp::Rect& clip) {
+ pp::Size view_size = view_->GetSize();
+ const bool view_was_empty = view_size.IsEmpty();
+ view_->UpdateView(position, clip, this);
+ if (view_was_empty)
+ ResetPositions();
+}
+
+bool Pong::HandleInputEvent(const pp::InputEvent& event) {
+ if (event.GetType() == PP_INPUTEVENT_TYPE_MOUSEUP) {
+ // By notifying the browser mouse clicks are handled, the application window
+ // is able to get focus and receive key events.
+ return true;
+ } else if (event.GetType() == PP_INPUTEVENT_TYPE_KEYUP) {
+ pp::KeyboardInputEvent key = pp::KeyboardInputEvent(event);
+ return view_->KeyUp(key);
+ } else if (event.GetType() == PP_INPUTEVENT_TYPE_KEYDOWN) {
+ pp::KeyboardInputEvent key = pp::KeyboardInputEvent(event);
+ return view_->KeyDown(key);
+ }
+ return false;
+}
+
+
+void Pong::HandleMessage(const pp::Var& var_message) {
+ if (!var_message.is_string())
+ return;
+ std::string message = var_message.AsString();
+ if (message == kResetScoreMethodId) {
+ ResetScore();
+ }
+}
+
+void Pong::Update() {
+ // Schedule another update
+ UpdateScheduler(kUpdateInterval, this);
+
+ const uint32_t key_code = view_->last_key_code();
+ if (key_code == kSpaceBar) {
+ ResetPositions();
+ return;
+ }
+ if (ball_.right() < court_.x()) {
+ ++computer_score_;
+ if (computer_score_ > kMaxPointsAllowed) {
+ ResetScore();
+ } else {
+ WriteScoreToFile();
+ UpdateScoreDisplay();
+ }
+ ResetPositions();
+ return;
+ }
+ if (ball_.x() > court_.right()) {
+ ++player_score_;
+ if (player_score_ > kMaxPointsAllowed) {
+ ResetScore();
+ } else {
+ WriteScoreToFile();
+ UpdateScoreDisplay();
+ }
+ ResetPositions();
+ return;
+ }
+ // Update human controlled paddle
+ if (key_code == kUpArrow) {
+ left_paddle_.Offset(0, -kUpdateDistance);
+ if (left_paddle_.y() - 1 < court_.y()) {
+ left_paddle_.Offset(0, court_.y() - left_paddle_.y() + 1);
+ }
+ } else if (key_code == kDownArrow) {
+ left_paddle_.Offset(0, kUpdateDistance);
+ if (left_paddle_.bottom() + 1 > court_.bottom()) {
+ left_paddle_.Offset(0, court_.bottom() - left_paddle_.bottom() - 1);
+ }
+ }
+
+ // Update AI controlled paddle
+ BallDirection direction = RightPaddleNextMove();
+ if (direction == kUpDirection) {
+ right_paddle_.Offset(0, -kUpdateDistance);
+ if (right_paddle_.y() < court_.y() + 1) {
+ right_paddle_.Offset(0, court_.y() - right_paddle_.y() + 1);
+ }
+ } else if (direction == kDownDirection) {
+ right_paddle_.Offset(0, kUpdateDistance);
+ if (right_paddle_.bottom() > court_.bottom() - 1) {
+ right_paddle_.Offset(0, court_.bottom() - right_paddle_.bottom() - 1);
+ }
+ }
+
+ // Bounce ball off bottom of screen
+ if (ball_.bottom() >= court_.bottom() - 1) {
+ ball_.Offset(0, court_.bottom() - ball_.bottom() - 1);
+ delta_y_ = -delta_y_;
+ }
+ // Bounce ball off top of screen
+ if (ball_.y() <= court_.y() + 1) {
+ ball_.Offset(0, court_.y() - ball_.y() + 1);
+ delta_y_ = -delta_y_;
+ }
+ // Bounce ball off human controlled paddle
+ if (left_paddle_.Intersects(ball_)) {
+ delta_x_ = abs(delta_x_);
+ if (ball_.CenterPoint().y() <
+ left_paddle_.y() + left_paddle_.height() / 5) {
+ delta_y_ -= kUpdateDistance;
+ if (delta_y_ == 0)
+ delta_y_ = -kUpdateDistance;
+ } else if (ball_.CenterPoint().y() >
+ left_paddle_.bottom() - left_paddle_.height() / 5) {
+ delta_y_ += kUpdateDistance;
+ if (delta_y_ == 0)
+ delta_y_ = kUpdateDistance;
+ }
+ }
+ // Bounce ball off ai controlled paddle
+ if (right_paddle_.Intersects(ball_)) {
+ delta_x_ = -abs(delta_x_);
+ if (ball_.CenterPoint().y() >
+ right_paddle_.bottom() - right_paddle_.height() / 5) {
+ delta_y_ += kUpdateDistance;
+ if (delta_y_ == 0)
+ delta_y_ = kUpdateDistance;
+ } else if (ball_.CenterPoint().y() <
+ right_paddle_.y() + right_paddle_.height() / 5) {
+ delta_y_ -= kUpdateDistance;
+ if (delta_y_ == 0)
+ delta_y_ -= kUpdateDistance;
+ }
+ }
+
+ // Move ball
+ ball_.Offset(delta_x_, delta_y_);
+
+ view_->set_ball_rect(ball_);
+ view_->set_left_paddle_rect(left_paddle_);
+ view_->set_right_paddle_rect(right_paddle_);
+ view_->Draw();
+}
+
+void Pong::UpdateScoreFromBuffer() {
+ size_t pos = bytes_buffer_.find_first_of(':');
+ player_score_ = ::atoi(bytes_buffer_.substr(0, pos).c_str());
+ computer_score_ = ::atoi(bytes_buffer_.substr(pos + 1).c_str());
+ UpdateScoreDisplay();
+}
+
+void Pong::UpdateScoreFromFile() {
+ file_io_ = new pp::FileIO(this);
+ file_io_->Open(*file_ref_,
+ PP_FILEOPENFLAG_READ | PP_FILEOPENFLAG_WRITE |
+ PP_FILEOPENFLAG_CREATE,
+ pp::CompletionCallback(AsyncCallbacks::FileOpenCallback,
+ this));
+}
+
+void Pong::WriteScoreToFile() {
+ if (file_io_ == NULL)
+ return;
+ // Write the score in <player score>:<computer score> format.
+ size_t score_string_length = 1 + (player_score_ ? log(player_score_) : 1) + 1
+ + (computer_score_ ? log(computer_score_) : 1) + 1;
+ bytes_buffer_.resize(score_string_length);
+ snprintf(&bytes_buffer_[0], bytes_buffer_.length(), "%i:%i", player_score_,
+ computer_score_);
+ offset_ = 0; // overwrite score in file.
+ file_io_->Write(offset_,
+ bytes_buffer_.c_str(),
+ bytes_buffer_.length(),
+ pp::CompletionCallback(AsyncCallbacks::WriteCallback, this));
+}
+
+void Pong::ResetPositions() {
+ pp::Size court_size = view_->GetSize();
+ pp::Rect court_rect(court_size);
+ court_.SetRect(court_rect);
+
+ pp::Rect rect;
+ rect.set_width(20);
+ rect.set_height(20);
+ rect.set_x((court_rect.x() + court_rect.width()) / 2 - rect.width() / 2);
+ rect.set_y(court_rect.y() + court_rect.height() - rect.height());
+ ball_.SetRect(rect);
+
+ const float paddle_width = 10;
+ const float paddle_height = 99;
+ const float paddle_pos_y =
+ (court_rect.y() + court_rect.height()) / 2 - rect.height() / 2;
+ rect.set_width(paddle_width);
+ rect.set_height(paddle_height);
+ rect.set_x(court_rect.x() + court_rect.width() / 5 + 1);
+ rect.set_y(paddle_pos_y);
+ left_paddle_.SetRect(rect);
+
+ rect.set_width(paddle_width);
+ rect.set_height(paddle_height);
+ rect.set_x(court_rect.x() + 4 * court_rect.width() / 5 - rect.width() - 1);
+ rect.set_y(paddle_pos_y);
+ right_paddle_.SetRect(rect);
+
+ delta_x_ = delta_y_ = kUpdateDistance;
+}
+
+void Pong::ResetScore() {
+ player_score_ = 0;
+ computer_score_ = 0;
+ UpdateScoreDisplay();
+}
+
+Pong::BallDirection Pong::RightPaddleNextMove() const {
+ static int32_t last_ball_y = 0;
+ int32_t ball_y = ball_.CenterPoint().y();
+ BallDirection ball_direction =
+ ball_y < last_ball_y ? kUpDirection : kDownDirection;
+ last_ball_y = ball_y;
+
+ if (ball_y < right_paddle_.y())
+ return kUpDirection;
+ if (ball_y > right_paddle_.bottom())
+ return kDownDirection;
+ return ball_direction;
+}
+
+void Pong::UpdateScoreDisplay() {
+ if (file_io_ == NULL)
+ return; // Since we cant't save the score to file, do nothing and return.
+ size_t message_length = 18 + (player_score_ ? log(player_score_) : 1) + 1
+ + (computer_score_ ? log(computer_score_) : 1) + 1;
+ std::string score_message(message_length, '\0');
+ snprintf(&score_message[0], score_message.length(),
+ "You: %i Computer: %i", player_score_, computer_score_);
+ PostMessage(pp::Var(score_message));
+}
+
+} // namespace pong
diff --git a/native_client_sdk/src/examples/pong/pong.h b/native_client_sdk/src/examples/pong/pong.h
new file mode 100644
index 0000000..8f38e3e
--- /dev/null
+++ b/native_client_sdk/src/examples/pong/pong.h
@@ -0,0 +1,92 @@
+// Copyright (c) 2011 The Native Client Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXAMPLES_PONG_PONG_H_
+#define EXAMPLES_PONG_PONG_H_
+
+#include <string>
+
+#include "ppapi/c/pp_file_info.h"
+#include "ppapi/cpp/graphics_2d.h"
+#include "ppapi/cpp/image_data.h"
+#include "ppapi/cpp/instance.h"
+#include "ppapi/cpp/rect.h"
+#include "ppapi/cpp/size.h"
+
+namespace pp {
+class FileIO;
+class FileRef;
+class FileSystem;
+class Rect;
+} // namespace pp
+
+namespace pong {
+
+class View;
+
+// The Instance class. One of these exists for each instance of your NaCl
+// module on the web page. The browser will ask the Module object to create
+// a new Instance for each occurrence of the <embed> tag that has these
+// attributes:
+// type="application/x-nacl"
+// nacl="pong.nmf"
+class Pong : public pp::Instance {
+ public:
+ explicit Pong(PP_Instance instance);
+ virtual ~Pong();
+
+ // Open the file (if available) that stores the game scores.
+ virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]);
+
+ // Update the graphics context to the new size, and regenerate |pixel_buffer_|
+ // to fit the new size as well.
+ virtual void DidChangeView(const pp::Rect& position, const pp::Rect& clip);
+
+ virtual bool HandleInputEvent(const pp::InputEvent& event);
+
+ // Called by the browser to handle the postMessage() call in Javascript.
+ // The message in this case is expected to contain the string 'update', or
+ // 'resetScore' in order to invoke either the Update or ResetScore function
+ // respectively.
+ virtual void HandleMessage(const pp::Var& var_message);
+
+ void Update();
+ void UpdateScoreFromBuffer();
+ void UpdateScoreFromFile();
+ void WriteScoreToFile();
+
+ PP_FileInfo file_info_;
+ int32_t bytes_to_read_;
+ int64_t offset_;
+ pp::FileIO* file_io_;
+ pp::FileRef* file_ref_;
+ pp::FileSystem* file_system_;
+ std::string bytes_buffer_;
+
+ private:
+ Pong(const Pong&); // Disallow copy
+
+ enum BallDirection {
+ kUpDirection = 0,
+ kDownDirection
+ };
+ void ResetPositions();
+ void ResetScore();
+ BallDirection RightPaddleNextMove() const;
+ void UpdateScoreDisplay();
+
+ View* view_;
+ pp::Rect left_paddle_;
+ pp::Rect right_paddle_;
+ pp::Rect ball_;
+ pp::Rect court_;
+ int32_t delta_x_;
+ int32_t delta_y_;
+ int player_score_;
+ int computer_score_;
+};
+
+} // namespace pong
+
+#endif // EXAMPLES_PONG_PONG_H_
diff --git a/native_client_sdk/src/examples/pong/pong.html b/native_client_sdk/src/examples/pong/pong.html
new file mode 100644
index 0000000..59a5915
--- /dev/null
+++ b/native_client_sdk/src/examples/pong/pong.html
@@ -0,0 +1,74 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+ <!--
+ Copyright (c) 2011 The Native Client Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style license that can be
+ found in the LICENSE file.
+ -->
+ <head>
+ <title>Pong</title>
+ <script type="text/javascript">
+ var pong = null;
+ var paintInterval = null;
+
+ // Handle a message coming from the NaCl module. The message payload is
+ // assumed to contain the current game score. Update the score text
+ // display with this value.
+ // Note that errors are also sent to this handler. A message starting
+ // with 'ERROR' is considered an error, all other strings are assumed
+ // to be scores.
+ function handleMessage(message_event) {
+ if (message_event.data.indexOf('ERROR') == 0) {
+ document.getElementById('error_log').innerHTML = message_event.data;
+ } else {
+ document.getElementById('score').innerHTML = message_event.data;
+ }
+ }
+
+ function pageDidUnload() {
+ clearInterval(paintInterval);
+ }
+ function resetScore() {
+ pong = document.getElementById('pong');
+ pong.postMessage("resetScore");
+ }
+ </script>
+ </head>
+ <body id="bodyId" onunload="pageDidUnload()">
+ <h1>Pong</h1>
+ <!-- Load the published .nexe. This includes the 'src' attribute which
+ shows how to load multi-architecture modules. Each entry in the "nexes"
+ object in the .nmf manifest file is a key-value pair: the key is the
+ runtime ('x86-32', 'x86-64', etc.); the value is a URL for the desired NaCl
+ module. To load the debug versions of your .nexes, set the 'src'
+ attribute to the _dbg.nmf version of the manifest file.
+
+ Note: The <EMBED> element is wrapped inside a <DIV>, which has both a 'load'
+ and a 'message' event listener attached. This wrapping method is used
+ instead of attaching the event listeners directly to the <EMBED> element to
+ ensure that the listeners are active before the NaCl module 'load' event
+ fires. This also allows you to use PPB_Messaging.PostMessage() (in C) or
+ pp::Instance.PostMessage() (in C++) from within the initialization code in
+ your NaCl module.
+ -->
+ <div id="listener">
+ <script type="text/javascript">
+ window.webkitStorageInfo.requestQuota(PERSISTENT, 1024);
+ var listener = document.getElementById('listener')
+ listener.addEventListener('message', handleMessage, true);
+ </script>
+
+ <embed name="nacl_module"
+ id="pong"
+ width=800 height=600
+ src="pong.nmf"
+ type="application/x-nacl" />
+ </div>
+ <br />
+ <p id="score">
+ </p>
+ <button onclick="resetScore()">Reset score</button>
+ <p id="error_log"></p>
+ </body>
+</html>
diff --git a/native_client_sdk/src/examples/pong/pong_module.cc b/native_client_sdk/src/examples/pong/pong_module.cc
new file mode 100644
index 0000000..c74ea28
--- /dev/null
+++ b/native_client_sdk/src/examples/pong/pong_module.cc
@@ -0,0 +1,34 @@
+// Copyright (c) 2011 The Native Client 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 <ppapi/cpp/module.h>
+
+#include "examples/pong/pong.h"
+
+namespace pong {
+// The Module class. The browser calls the CreateInstance() method to create
+// an instance of your NaCl module on the web page. The browser creates a new
+// instance for each <embed> tag with type="application/x-nacl".
+class PongModule : public pp::Module {
+ public:
+ PongModule() : pp::Module() {}
+ virtual ~PongModule() {}
+
+ // Create and return a PiGeneratorInstance object.
+ virtual pp::Instance* CreateInstance(PP_Instance instance) {
+ return new Pong(instance);
+ }
+};
+} // namespace pong
+
+// Factory function called by the browser when the module is first loaded.
+// The browser keeps a singleton of this module. It calls the
+// CreateInstance() method on the object you return to make instances. There
+// is one instance per <embed> tag on the page. This is the main binding
+// point for your NaCl module with the browser.
+namespace pp {
+Module* CreateModule() {
+ return new pong::PongModule();
+}
+} // namespace pp
diff --git a/native_client_sdk/src/examples/pong/view.cc b/native_client_sdk/src/examples/pong/view.cc
new file mode 100644
index 0000000..4bb11b9
--- /dev/null
+++ b/native_client_sdk/src/examples/pong/view.cc
@@ -0,0 +1,162 @@
+// Copyright (c) 2011 The Native Client 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 "examples/pong/view.h"
+
+#include <math.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "ppapi/cpp/completion_callback.h"
+#include "ppapi/cpp/graphics_2d.h"
+#include "ppapi/cpp/image_data.h"
+#include "ppapi/cpp/input_event.h"
+#include "ppapi/cpp/instance.h"
+#include "ppapi/cpp/point.h"
+#include "ppapi/cpp/var.h"
+
+// Input event key codes. PPAPI uses Windows Virtual key codes.
+const uint32_t kSpaceBar = 0x20;
+const uint32_t kUpArrow = 0x26;
+const uint32_t kDownArrow = 0x28;
+
+namespace {
+
+const uint32_t kOpaqueColorMask = 0xff000000; // Opaque pixels.
+const uint32_t kWhiteMask = 0xffffff;
+
+// This is called by the browser when the 2D context has been flushed to the
+// browser window.
+void FlushCallback(void* data, int32_t result) {
+ static_cast<pong::View*>(data)->set_flush_pending(false);
+}
+
+} // namespace
+
+namespace pong {
+View::View(pp::Instance* instance)
+ : instance_(instance), last_key_code_(0x0), flush_pending_(false),
+ graphics_2d_context_(NULL), pixel_buffer_(NULL) {}
+
+View::~View() {
+ DestroyContext();
+ delete pixel_buffer_;
+}
+
+pp::Size View::GetSize() const {
+ pp::Size size;
+ if (graphics_2d_context_) {
+ size.SetSize(graphics_2d_context_->size().width(),
+ graphics_2d_context_->size().height());
+ }
+ return size;
+}
+
+bool View::KeyDown(const pp::KeyboardInputEvent& key) {
+ last_key_code_ = key.GetKeyCode();
+ if (last_key_code_ == kSpaceBar || last_key_code_ == kUpArrow ||
+ last_key_code_ == kDownArrow)
+ return true;
+ return false;
+}
+
+bool View::KeyUp(const pp::KeyboardInputEvent& key) {
+ if (last_key_code_ == key.GetKeyCode()) {
+ last_key_code_ = 0x0; // Indicates key code is not set.
+ }
+ return false;
+}
+
+void View::Draw() {
+ uint32_t* pixels = static_cast<uint32_t*>(pixel_buffer_->data());
+ if (NULL == pixels)
+ return;
+ // Clear the buffer
+ const int32_t height = pixel_buffer_->size().height();
+ const int32_t width = pixel_buffer_->size().width();
+ for (int32_t py = 0; py < height; ++py) {
+ for (int32_t px = 0; px < width; ++px) {
+ const int32_t pos = px + py * width;
+ uint32_t color = kOpaqueColorMask;
+ // Draw the paddles
+ if (left_paddle_rect_.Contains(px, py) ||
+ right_paddle_rect_.Contains(px, py)) {
+ color |= kWhiteMask;
+ } else {
+ pp::Point center_point = ball_rect_.CenterPoint();
+ float radius = ball_rect_.width() / 2;
+ float distance_x = px - center_point.x();
+ float distance_y = py - center_point.y();
+ float distance =
+ sqrt(distance_x * distance_x + distance_y * distance_y);
+ // Draw the ball
+ if (distance <= radius)
+ color |= kWhiteMask;
+ }
+ pixels[pos] = color;
+ }
+ }
+
+ FlushPixelBuffer();
+}
+
+void View::UpdateView(const pp::Rect& position,
+ const pp::Rect& clip,
+ pp::Instance* instance) {
+ const int32_t width =
+ pixel_buffer_ ? pixel_buffer_->size().width() : 0;
+ const int32_t height =
+ pixel_buffer_ ? pixel_buffer_->size().height() : 0;
+
+ if (position.size().width() == width &&
+ position.size().height() == height)
+ return; // Size didn't change, no need to update anything.
+
+ // Create a new device context with the new size.
+ DestroyContext();
+ CreateContext(position.size(), instance);
+ // Delete the old pixel buffer and create a new one.
+ delete pixel_buffer_;
+ pixel_buffer_ = NULL;
+ if (graphics_2d_context_ != NULL) {
+ pixel_buffer_ = new pp::ImageData(instance, PP_IMAGEDATAFORMAT_BGRA_PREMUL,
+ graphics_2d_context_->size(),
+ false);
+ }
+}
+
+void View::CreateContext(const pp::Size& size, pp::Instance* instance) {
+ if (IsContextValid())
+ return;
+ graphics_2d_context_ = new pp::Graphics2D(instance, size,
+ false);
+ if (!instance->BindGraphics(*graphics_2d_context_)) {
+ instance_->PostMessage(pp::Var("ERROR: Couldn't bind the device context"));
+ }
+}
+
+void View::DestroyContext() {
+ if (!IsContextValid())
+ return;
+ delete graphics_2d_context_;
+ graphics_2d_context_ = NULL;
+}
+
+void View::FlushPixelBuffer() {
+ if (!IsContextValid())
+ return;
+ // Note that the pixel lock is held while the buffer is copied into the
+ // device context and then flushed.
+ graphics_2d_context_->PaintImageData(*pixel_buffer_, pp::Point());
+ if (flush_pending_)
+ return;
+ flush_pending_ = true;
+ graphics_2d_context_->Flush(pp::CompletionCallback(&FlushCallback, this));
+}
+
+bool View::IsContextValid() const {
+ return graphics_2d_context_ != NULL;
+}
+
+} // namespace pong
diff --git a/native_client_sdk/src/examples/pong/view.h b/native_client_sdk/src/examples/pong/view.h
new file mode 100644
index 0000000..269ff99
--- /dev/null
+++ b/native_client_sdk/src/examples/pong/view.h
@@ -0,0 +1,75 @@
+// Copyright (c) 2011 The Native Client Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXAMPLES_PONG_VIEW_H_
+#define EXAMPLES_PONG_VIEW_H_
+
+#include "ppapi/cpp/rect.h"
+
+namespace pp {
+class Graphics2D;
+class ImageData;
+class Instance;
+class KeyboardInputEvent;
+class Rect;
+class Size;
+} // namespace pp
+
+namespace pong {
+
+class View {
+ public:
+ explicit View(pp::Instance* instance);
+ ~View();
+
+ const uint32_t& last_key_code() const {
+ return last_key_code_;
+ }
+ void set_left_paddle_rect(const pp::Rect& left_paddle_rect) {
+ left_paddle_rect_ = left_paddle_rect;
+ }
+ void set_right_paddle_rect(const pp::Rect& right_paddle_rect) {
+ right_paddle_rect_ = right_paddle_rect;
+ }
+ void set_ball_rect(const pp::Rect& ball_rect) {
+ ball_rect_ = ball_rect;
+ }
+ void set_flush_pending(bool flush_pending) {
+ flush_pending_ = flush_pending;
+ }
+ pp::Size GetSize() const;
+ bool KeyDown(const pp::KeyboardInputEvent& key);
+ bool KeyUp(const pp::KeyboardInputEvent& key);
+ void Draw();
+ void UpdateView(const pp::Rect& position,
+ const pp::Rect& clip,
+ pp::Instance* instance);
+
+ private:
+ pp::Instance* const instance_; // weak
+ // Create and initialize the 2D context used for drawing.
+ void CreateContext(const pp::Size& size, pp::Instance* instance);
+ // Destroy the 2D drawing context.
+ void DestroyContext();
+ // Push the pixels to the browser, then attempt to flush the 2D context. If
+ // there is a pending flush on the 2D context, then update the pixels only
+ // and do not flush.
+ void FlushPixelBuffer();
+ bool IsContextValid() const;
+
+ uint32_t last_key_code_;
+ // Geometry for drawing
+ pp::Rect left_paddle_rect_;
+ pp::Rect right_paddle_rect_;
+ pp::Rect ball_rect_;
+ // Drawing stuff
+ bool flush_pending_;
+ pp::Graphics2D* graphics_2d_context_;
+ pp::ImageData* pixel_buffer_;
+};
+
+
+} // namespace pong
+
+#endif // EXAMPLES_PONG_VIEW_H_
diff --git a/native_client_sdk/src/examples/scons b/native_client_sdk/src/examples/scons
new file mode 100755
index 0000000..7742fd7
--- /dev/null
+++ b/native_client_sdk/src/examples/scons
@@ -0,0 +1,39 @@
+#!/bin/bash
+#
+# Copyright (c) 2011 The Native Client Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+
+readonly SCRIPT_DIR="$(dirname "$0")"
+readonly SCRIPT_DIR_ABS="$(cd "${SCRIPT_DIR}" ; pwd -P)"
+
+# NACL_SDK_ROOT must be set.
+if [ x"${NACL_SDK_ROOT}"x == "xx" ] ; then
+ echo "Error: NACL_SDK_ROOT is not set."
+ exit 1;
+fi
+
+# NACL_TARGET_PLATFORM is really the name of a folder with the base dir -
+# usually NACL_SDK_ROOT - within which the toolchain for the target platform
+# are found.
+# Replace the platform with the name of your target platform. For example, to
+# build applications that target the pepper_17 API, set
+# NACL_TARGET_PLATFORM="pepper_17"
+if [ x"${NACL_TARGET_PLATFORM}"x == "xx" ] ; then
+ export NACL_TARGET_PLATFORM="pepper_17"
+fi
+
+readonly NACL_PLATFORM_DIR="${NACL_SDK_ROOT}/${NACL_TARGET_PLATFORM}"
+readonly BASE_SCRIPT="${NACL_PLATFORM_DIR}/third_party/scons-2.0.1/script/scons"
+
+export SCONS_LIB_DIR="${NACL_PLATFORM_DIR}/third_party/scons-2.0.1/engine"
+export PYTHONPATH="${NACL_PLATFORM_DIR}/third_party/scons-2.0.1/engine"
+# We have to do this because scons overrides PYTHONPATH and does not preserve
+# what is provided by the OS. The custom variable name won't be overwritten.
+export PYMOX="${NACL_PLATFORM_DIR}/third_party/pymox"
+
+"${BASE_SCRIPT}" --file=build.scons \
+ --site-dir="${NACL_PLATFORM_DIR}/build_tools/nacl_sdk_scons" \
+ $*
+
diff --git a/native_client_sdk/src/examples/scons.bat b/native_client_sdk/src/examples/scons.bat
new file mode 100755
index 0000000..2a67758
--- /dev/null
+++ b/native_client_sdk/src/examples/scons.bat
@@ -0,0 +1,43 @@
+@echo off
+
+:: Copyright (c) 2011 The Native Client Authors. All rights reserved.
+:: Use of this source code is governed by a BSD-style license that can be
+:: found in the LICENSE file.
+
+setlocal
+
+:: NACL_SDK_ROOT must be set.
+if not defined NACL_SDK_ROOT (
+ echo Error: NACL_SDK_ROOT is not set.
+ echo Please set NACL_SDK_ROOT to the full path of the Native Client SDK.
+ echo For example:
+ echo set NACL_SDK_ROOT=D:\nacl_sdk
+ goto end
+)
+
+:: NACL_TARGET_PLATFORM is really the name of a folder with the base dir -
+:: usually NACL_SDK_ROOT - within which the toolchain for the target platform
+:: are found.
+:: Replace the platform with the name of your target platform. For example, to
+:: build applications that target the pepper_17 API, set
+:: NACL_TARGET_PLATFORM=pepper_17
+if not defined NACL_TARGET_PLATFORM (
+ set NACL_TARGET_PLATFORM=pepper_17
+)
+
+set NACL_PLATFORM_DIR=%NACL_SDK_ROOT%\%NACL_TARGET_PLATFORM%
+
+set SCONS_LIB_DIR=%NACL_PLATFORM_DIR%\third_party\scons-2.0.1\engine
+set PYTHONPATH=%NACL_PLATFORM_DIR%\third_party\scons-2.0.1\engine
+
+:: We have to do this because scons overrides PYTHONPATH and does not preserve
+:: what is provided by the OS. The custom variable name won't be overwritten.
+set PYMOX=%NACL_PLATFORM_DIR%\third_party\pymox
+
+:: Run the included copy of scons.
+python -O -OO "%NACL_PLATFORM_DIR%\third_party\scons-2.0.1\script\scons" ^
+--warn no-visual-c-missing ^
+--file=build.scons ^
+--site-dir="%NACL_PLATFORM_DIR%\build_tools\nacl_sdk_scons" %*
+
+:end
diff --git a/native_client_sdk/src/examples/sine_synth/build.scons b/native_client_sdk/src/examples/sine_synth/build.scons
new file mode 100644
index 0000000..5b10dc8
--- /dev/null
+++ b/native_client_sdk/src/examples/sine_synth/build.scons
@@ -0,0 +1,40 @@
+#! -*- python -*-
+#
+# Copyright (c) 2011 The Native Client Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import make_nacl_env
+import os
+
+nacl_env = make_nacl_env.NaClEnvironment(
+ use_c_plus_plus_libs=True, nacl_platform=os.getenv('NACL_TARGET_PLATFORM'),
+ install_subdir='sine_synth', lib_prefix='..')
+nacl_env.Append(
+ # Add a CPPPATH that enables the full-path #include directives, such as
+ # #include "examples/sine_synth/sine_synth.h"
+ CPPPATH=[os.path.dirname(os.path.dirname(os.getcwd()))],
+ # Strict ANSI compliance.
+ EXTRA_CCFLAGS=['-pedantic'],
+ )
+
+sources = ['sine_synth.cc']
+
+opt_nexes, dbg_nexes = nacl_env.AllNaClModules(sources, 'sine_synth')
+
+# This target is used by the SDK build system to provide a prebuilt version
+# of the example in the SDK installer.
+nacl_env.InstallPrebuilt('sine_synth')
+
+app_files = [
+ 'sine_synth.html',
+ 'sine_synth.nmf',
+ ]
+
+# Split the install of the .nexes from the other app sources so that the strip
+# action is applied to the .nexes only.
+install_nexes = nacl_env.NaClStrippedInstall(dir=nacl_env['NACL_INSTALL_ROOT'],
+ source=opt_nexes)
+install_app = nacl_env.Install(dir=nacl_env['NACL_INSTALL_ROOT'],
+ source=app_files)
+nacl_env.Alias('install', source=install_app + install_nexes)
diff --git a/native_client_sdk/src/examples/sine_synth/sine_synth.cc b/native_client_sdk/src/examples/sine_synth/sine_synth.cc
new file mode 100644
index 0000000..2305020
--- /dev/null
+++ b/native_client_sdk/src/examples/sine_synth/sine_synth.cc
@@ -0,0 +1,183 @@
+// Copyright (c) 2011 The Native Client 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 <cassert>
+#include <cmath>
+#include <limits>
+#include <sstream>
+#include "ppapi/cpp/audio.h"
+#include "ppapi/cpp/instance.h"
+#include "ppapi/cpp/module.h"
+#include "ppapi/cpp/var.h"
+
+namespace {
+const char* const kPlaySoundId = "playSound";
+const char* const kStopSoundId = "stopSound";
+const char* const kSetFrequencyId = "setFrequency";
+static const char kMessageArgumentSeparator = ':';
+
+const double kDefaultFrequency = 440.0;
+const double kPi = 3.141592653589;
+const double kTwoPi = 2.0 * kPi;
+// The sample count we will request.
+const uint32_t kSampleFrameCount = 4096u;
+// Only supporting stereo audio for now.
+const uint32_t kChannels = 2u;
+} // namespace
+
+namespace sine_synth {
+// The Instance class. One of these exists for each instance of your NaCl
+// module on the web page. The browser will ask the Module object to create
+// a new Instance for each occurrence of the <embed> tag that has these
+// attributes:
+// type="application/x-nacl"
+// src="sine_synth.nmf"
+class SineSynthInstance : public pp::Instance {
+ public:
+ explicit SineSynthInstance(PP_Instance instance)
+ : pp::Instance(instance),
+ frequency_(kDefaultFrequency),
+ theta_(0),
+ sample_frame_count_(kSampleFrameCount) {}
+ virtual ~SineSynthInstance() {}
+
+ // Called by the browser once the NaCl module is loaded and ready to
+ // initialize. Creates a Pepper audio context and initializes it. Returns
+ // true on success. Returning false causes the NaCl module to be deleted and
+ // no other functions to be called.
+ virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]);
+
+ // Called by the browser to handle the postMessage() call in Javascript.
+ // |var_message| is expected to be a string that contains the name of the
+ // method to call. Note that the setFrequency method takes a single
+ // parameter, the frequency. The frequency parameter is encoded as a string
+ // and appended to the 'setFrequency' method name after a ':'. Examples
+ // of possible message strings are:
+ // playSound
+ // stopSound
+ // setFrequency:880
+ // If |var_message| is not a recognized method name, this method does nothing.
+ virtual void HandleMessage(const pp::Var& var_message);
+
+ // Set the frequency of the sine wave to |frequency|. Posts a message back
+ // to the browser with the new frequency value.
+ void SetFrequency(double frequency);
+
+ // The frequency property accessor.
+ double frequency() const { return frequency_; }
+
+ private:
+ static void SineWaveCallback(void* samples,
+ uint32_t buffer_size,
+ void* data) {
+ SineSynthInstance* sine_synth_instance =
+ reinterpret_cast<SineSynthInstance*>(data);
+ const double frequency = sine_synth_instance->frequency();
+ const double delta = kTwoPi * frequency / PP_AUDIOSAMPLERATE_44100;
+ const int16_t max_int16 = std::numeric_limits<int16_t>::max();
+
+ int16_t* buff = reinterpret_cast<int16_t*>(samples);
+
+ // Make sure we can't write outside the buffer.
+ assert(buffer_size >= (sizeof(*buff) * kChannels *
+ sine_synth_instance->sample_frame_count_));
+
+ for (size_t sample_i = 0;
+ sample_i < sine_synth_instance->sample_frame_count_;
+ ++sample_i, sine_synth_instance->theta_ += delta) {
+ // Keep theta_ from going beyond 2*Pi.
+ if (sine_synth_instance->theta_ > kTwoPi) {
+ sine_synth_instance->theta_ -= kTwoPi;
+ }
+ double sin_value(std::sin(sine_synth_instance->theta_));
+ int16_t scaled_value = static_cast<int16_t>(sin_value * max_int16);
+ for (size_t channel = 0; channel < kChannels; ++channel) {
+ *buff++ = scaled_value;
+ }
+ }
+ }
+
+ pp::Audio audio_;
+ double frequency_;
+
+ // The last parameter sent to the sin function. Used to prevent sine wave
+ // skips on buffer boundaries.
+ double theta_;
+
+ // The count of sample frames per channel in an audio buffer.
+ uint32_t sample_frame_count_;
+};
+
+bool SineSynthInstance::Init(uint32_t argc,
+ const char* argn[],
+ const char* argv[]) {
+ // Ask the device for an appropriate sample count size.
+ sample_frame_count_ =
+ pp::AudioConfig::RecommendSampleFrameCount(PP_AUDIOSAMPLERATE_44100,
+ kSampleFrameCount);
+ audio_ = pp::Audio(this,
+ pp::AudioConfig(this,
+ PP_AUDIOSAMPLERATE_44100,
+ sample_frame_count_),
+ SineWaveCallback,
+ this);
+ return true;
+}
+
+void SineSynthInstance::HandleMessage(const pp::Var& var_message) {
+ if (!var_message.is_string()) {
+ return;
+ }
+ std::string message = var_message.AsString();
+ if (message == kPlaySoundId) {
+ audio_.StartPlayback();
+ } else if (message == kStopSoundId) {
+ audio_.StopPlayback();
+ } else if (message.find(kSetFrequencyId) == 0) {
+ // The argument to setFrequency is everything after the first ':'.
+ size_t sep_pos = message.find_first_of(kMessageArgumentSeparator);
+ if (sep_pos != std::string::npos) {
+ std::string string_arg = message.substr(sep_pos + 1);
+ // Got the argument value as a string: try to convert it to a number.
+ std::istringstream stream(string_arg);
+ double double_value;
+ if (stream >> double_value) {
+ SetFrequency(double_value);
+ return;
+ }
+ }
+ }
+}
+
+void SineSynthInstance::SetFrequency(double frequency) {
+ frequency_ = frequency;
+ PostMessage(pp::Var(frequency_));
+}
+
+// The Module class. The browser calls the CreateInstance() method to create
+// an instance of your NaCl module on the web page. The browser creates a new
+// instance for each <embed> tag with type="application/x-nacl".
+class SineSynthModule : public pp::Module {
+ public:
+ SineSynthModule() : pp::Module() {}
+ ~SineSynthModule() {}
+
+ // Create and return a HelloWorldInstance object.
+ virtual pp::Instance* CreateInstance(PP_Instance instance) {
+ return new SineSynthInstance(instance);
+ }
+};
+
+} // namespace sine_synth
+
+// Factory function called by the browser when the module is first loaded.
+// The browser keeps a singleton of this module. It calls the
+// CreateInstance() method on the object you return to make instances. There
+// is one instance per <embed> tag on the page. This is the main binding
+// point for your NaCl module with the browser.
+namespace pp {
+Module* CreateModule() {
+ return new sine_synth::SineSynthModule();
+}
+} // namespace pp
diff --git a/native_client_sdk/src/examples/sine_synth/sine_synth.html b/native_client_sdk/src/examples/sine_synth/sine_synth.html
new file mode 100644
index 0000000..3d1609b
--- /dev/null
+++ b/native_client_sdk/src/examples/sine_synth/sine_synth.html
@@ -0,0 +1,80 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+<!--
+Copyright (c) 2011 The Native Client Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+<head>
+ <title>Sine Wave Synthesizer</title>
+ <script type="text/javascript">
+ sineSynth = null; // Global application object.
+
+ // Indicate success when the NaCl module has loaded.
+ function moduleDidLoad() {
+ sineSynth = document.getElementById('sineSynth');
+ document.getElementById('frequency_field').value = 440;
+ }
+
+ // Handle a message coming from the NaCl module. The message payload
+ // contains the frequency value. Update the frequency field with this
+ // value.
+ function handleMessage(message_event) {
+ document.getElementById('frequency_field').value = message_event.data;
+ }
+
+ function toggleSound(flag) {
+ sineSynth.postMessage('setFrequency:' +
+ document.getElementById('frequency_field').value);
+ if (flag) {
+ sineSynth.postMessage('playSound');
+ } else {
+ sineSynth.postMessage('stopSound');
+ }
+ }
+
+ function changeFrequency(freq) {
+ sineSynth.postMessage('setFrequency:' + freq);
+ }
+ </script>
+</head>
+
+<body id="bodyId">
+ <h1>Sine Wave Synthesizer</h1>
+ <p>Click the button to start and stop the sine wave playing.</p>
+ <button onclick="toggleSound(true)">Play</button>
+ <button onclick="toggleSound(false)">Stop</button>
+ <p>Enter the frequency of the sine wave:</p>
+ <input type="text" size="15" id="frequency_field"
+ value="#undef" onchange="changeFrequency(this.value)" />
+ <!-- Load the published .nexe. This includes the 'src' attribute which
+ shows how to load multi-architecture modules. Each entry in the "nexes"
+ object in the .nmf manifest file is a key-value pair: the key is the runtime
+ ('x86-32', 'x86-64', etc.); the value is a URL for the desired NaCl module.
+ To load the debug versions of your .nexes, set the 'src' attribute to the
+ _dbg.nmf version of the manifest file.
+
+ Note: The <EMBED> element is wrapped inside a <DIV>, which has both a 'load'
+ and a 'message' event listener attached. This wrapping method is used
+ instead of attaching the event listeners directly to the <EMBED> element to
+ ensure that the listeners are active before the NaCl module 'load' event
+ fires. This also allows you to use PPB_Messaging.PostMessage() (in C) or
+ pp::Instance.PostMessage() (in C++) from within the initialization code in
+ your NaCl module.
+ -->
+ <div id="listener">
+ <script type="text/javascript">
+ var listener = document.getElementById('listener')
+ listener.addEventListener('load', moduleDidLoad, true);
+ listener.addEventListener('message', handleMessage, true);
+ </script>
+
+ <embed name="nacl_module"
+ id="sineSynth"
+ width=0 height=0
+ src="sine_synth.nmf"
+ type="application/x-nacl" />
+ </div>
+</body>
+</html>
diff --git a/native_client_sdk/src/examples/tumbler/bind.js b/native_client_sdk/src/examples/tumbler/bind.js
new file mode 100644
index 0000000..92fbbd2
--- /dev/null
+++ b/native_client_sdk/src/examples/tumbler/bind.js
@@ -0,0 +1,22 @@
+// Copyright (c) 2011 The Native Client Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview This class implements an extension to Function object that
+ * lets you bind a scope for |this| to a function.
+ */
+
+/**
+ * Bind a scope to a function. Used to bind an object to |this| for event
+ * handlers.
+ * @param {!Object} scope The scope in which the function executes. |scope|
+ * becomes |this| during function execution.
+ * @return {function} the bound version of the original function.
+ */
+Function.prototype.bind = function(scope) {
+ var boundContext = this;
+ return function() {
+ return boundContext.apply(scope, arguments);
+ }
+}
diff --git a/native_client_sdk/src/examples/tumbler/build.scons b/native_client_sdk/src/examples/tumbler/build.scons
new file mode 100644
index 0000000..4c42b7c
--- /dev/null
+++ b/native_client_sdk/src/examples/tumbler/build.scons
@@ -0,0 +1,55 @@
+#! -*- python -*-
+#
+# Copyright (c) 2011 The Native Client Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import make_nacl_env
+import nacl_utils
+import os
+
+nacl_env = make_nacl_env.NaClEnvironment(
+ use_c_plus_plus_libs=True, nacl_platform=os.getenv('NACL_TARGET_PLATFORM'),
+ install_subdir='tumbler', lib_prefix='..')
+nacl_env.Append(
+ # Add a CPPPATH that enables the full-path #include directives, such as
+ # #include "examples/sine_synth/sine_synth.h"
+ CPPPATH=[os.path.dirname(os.path.dirname(os.getcwd()))],
+ # Strict ANSI compliance.
+ EXTRA_CCFLAGS=['-pedantic'],
+ LIBS=['ppapi_gles2'],
+ )
+
+sources = [
+ 'cube.cc',
+ 'opengl_context.cc',
+ 'scripting_bridge.cc',
+ 'shader_util.cc',
+ 'transforms.cc',
+ 'tumbler.cc',
+ 'tumbler_module.cc',
+ ]
+
+opt_nexes, dbg_nexes = nacl_env.AllNaClModules(sources, 'tumbler')
+
+# This target is used by the SDK build system to provide a prebuilt version
+# of the example in the SDK installer.
+nacl_env.InstallPrebuilt('tumbler')
+
+app_files = [
+ 'tumbler.html',
+ 'tumbler.nmf',
+ 'bind.js',
+ 'dragger.js',
+ 'trackball.js',
+ 'tumbler.js',
+ 'vector3.js',
+ ]
+
+# Split the install of the .nexes from the other app sources so that the strip
+# action is applied to the .nexes only.
+install_nexes = nacl_env.NaClStrippedInstall(dir=nacl_env['NACL_INSTALL_ROOT'],
+ source=opt_nexes)
+install_app = nacl_env.Install(dir=nacl_env['NACL_INSTALL_ROOT'],
+ source=app_files)
+nacl_env.Alias('install', source=install_app + install_nexes)
diff --git a/native_client_sdk/src/examples/tumbler/callback.h b/native_client_sdk/src/examples/tumbler/callback.h
new file mode 100644
index 0000000..4d67262
--- /dev/null
+++ b/native_client_sdk/src/examples/tumbler/callback.h
@@ -0,0 +1,92 @@
+// Copyright (c) 2011 The Native Client Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXAMPLES_TUMBLER_CALLBACK_H_
+#define EXAMPLES_TUMBLER_CALLBACK_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+namespace tumbler {
+
+class ScriptingBridge;
+
+// Templates used to support method call-backs when a method or property is
+// accessed from the browser code.
+
+// Class suite used to publish a method name to Javascript. Typical use is
+// like this:
+// photo::MethodCallback<Calculator>* calculate_callback_;
+// calculate_callback_ =
+// new scripting::MethodCallback<Calculator>(this,
+// &Calculator::Calculate);
+// bridge->AddMethodNamed("calculate", calculate_callback_);
+// ...
+// delete calculate_callback_;
+//
+// The caller must delete the callback.
+
+// Methods get parameters as a dictionary that maps parameter names to values.
+typedef std::map<std::string, std::string> MethodParameter;
+
+// Pure virtual class used in STL containers.
+class MethodCallbackExecutor {
+ public:
+ virtual ~MethodCallbackExecutor() {}
+ virtual void Execute(
+ const ScriptingBridge& bridge,
+ const MethodParameter& parameters) = 0;
+};
+
+template <class T>
+class MethodCallback : public MethodCallbackExecutor {
+ public:
+ typedef void (T::*Method)(
+ const ScriptingBridge& bridge,
+ const MethodParameter& parameters);
+
+ MethodCallback(T* instance, Method method)
+ : instance_(instance), method_(method) {}
+ virtual ~MethodCallback() {}
+ virtual void Execute(
+ const ScriptingBridge& bridge,
+ const MethodParameter& parameters) {
+ // Use "this->" to force C++ to look inside our templatized base class; see
+ // Effective C++, 3rd Ed, item 43, p210 for details.
+ ((this->instance_)->*(this->method_))(bridge, parameters);
+ }
+
+ private:
+ T* instance_;
+ Method method_;
+};
+
+template <class T>
+class ConstMethodCallback : public MethodCallbackExecutor {
+ public:
+ typedef void (T::*ConstMethod)(
+ const ScriptingBridge& bridge,
+ const MethodParameter& parameters) const;
+
+ ConstMethodCallback(const T* instance, ConstMethod method)
+ : instance_(instance), const_method_(method) {}
+ virtual ~ConstMethodCallback() {}
+ virtual void Execute(
+ const ScriptingBridge& bridge,
+ const MethodParameter& parameters) {
+ // Use "this->" to force C++ to look inside our templatized base class; see
+ // Effective C++, 3rd Ed, item 43, p210 for details.
+ ((this->instance_)->*(this->const_method_))(bridge, parameters);
+ }
+
+ private:
+ const T* instance_;
+ ConstMethod const_method_;
+};
+
+} // namespace tumbler
+
+#endif // EXAMPLES_TUMBLER_CALLBACK_H_
+
diff --git a/native_client_sdk/src/examples/tumbler/cube.cc b/native_client_sdk/src/examples/tumbler/cube.cc
new file mode 100644
index 0000000..c062c81
--- /dev/null
+++ b/native_client_sdk/src/examples/tumbler/cube.cc
@@ -0,0 +1,267 @@
+// Copyright (c) 2011 The Native Client 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 "examples/tumbler/cube.h"
+
+#include <algorithm>
+
+#include "examples/tumbler/shader_util.h"
+#include "examples/tumbler/transforms.h"
+
+namespace tumbler {
+
+static const size_t kVertexCount = 24;
+static const int kIndexCount = 36;
+
+Cube::Cube(SharedOpenGLContext opengl_context)
+ : opengl_context_(opengl_context),
+ width_(1),
+ height_(1) {
+ eye_[0] = eye_[1] = 0.0f;
+ eye_[2] = 2.0f;
+ orientation_[0] = 0.0f;
+ orientation_[1] = 0.0f;
+ orientation_[2] = 0.0f;
+ orientation_[3] = 1.0f;
+}
+
+Cube::~Cube() {
+ glDeleteBuffers(3, cube_vbos_);
+ glDeleteProgram(shader_program_object_);
+}
+
+void Cube::PrepareOpenGL() {
+ CreateShaders();
+ CreateCube();
+ glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
+ glEnable(GL_DEPTH_TEST);
+}
+
+void Cube::Resize(int width, int height) {
+ width_ = std::max(width, 1);
+ height_ = std::max(height, 1);
+ // Set the viewport
+ glViewport(0, 0, width_, height_);
+ // Compute the perspective projection matrix with a 60 degree FOV.
+ GLfloat aspect = static_cast<GLfloat>(width_) / static_cast<GLfloat>(height_);
+ transform_4x4::LoadIdentity(perspective_proj_);
+ transform_4x4::Perspective(perspective_proj_, 60.0f, aspect, 1.0f, 20.0f);
+}
+
+void Cube::Draw() {
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ // Compute a new model-view matrix, then use that to make the composite
+ // model-view-projection matrix: MVP = MV . P.
+ GLfloat model_view[16];
+ ComputeModelViewTransform(model_view);
+ transform_4x4::Multiply(mvp_matrix_, model_view, perspective_proj_);
+
+ glBindBuffer(GL_ARRAY_BUFFER, cube_vbos_[0]);
+ glUseProgram(shader_program_object_);
+ glEnableVertexAttribArray(position_location_);
+ glVertexAttribPointer(position_location_,
+ 3,
+ GL_FLOAT,
+ GL_FALSE,
+ 3 * sizeof(GLfloat),
+ NULL);
+ glEnableVertexAttribArray(color_location_);
+ glBindBuffer(GL_ARRAY_BUFFER, cube_vbos_[1]);
+ glVertexAttribPointer(color_location_,
+ 3,
+ GL_FLOAT,
+ GL_FALSE,
+ 3 * sizeof(GLfloat),
+ NULL);
+ glUniformMatrix4fv(mvp_location_, 1, GL_FALSE, mvp_matrix_);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cube_vbos_[2]);
+ glDrawElements(GL_TRIANGLES, kIndexCount, GL_UNSIGNED_SHORT, 0);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+}
+
+bool Cube::CreateShaders() {
+ const char vertex_shader_src[] =
+ "uniform mat4 u_mvpMatrix; \n"
+ "attribute vec4 a_position; \n"
+ "attribute vec3 a_color; \n"
+ "varying lowp vec4 v_color; \n"
+ "void main() \n"
+ "{ \n"
+ " v_color.xyz = a_color; \n"
+ " v_color.w = 1.0; \n"
+ " gl_Position = u_mvpMatrix * a_position; \n"
+ "} \n";
+
+ const char fragment_shader_src[] =
+ "varying lowp vec4 v_color; \n"
+ "void main() \n"
+ "{ \n"
+ " gl_FragColor = v_color; \n"
+ "} \n";
+
+ // Load the shaders and get a linked program object
+ shader_program_object_ =
+ shader_util::CreateProgramFromVertexAndFragmentShaders(
+ vertex_shader_src, fragment_shader_src);
+ if (shader_program_object_ == 0)
+ return false;
+ position_location_ = glGetAttribLocation(shader_program_object_,
+ "a_position");
+ color_location_ = glGetAttribLocation(shader_program_object_, "a_color");
+ mvp_location_ = glGetUniformLocation(shader_program_object_, "u_mvpMatrix");
+ return true;
+}
+
+void Cube::CreateCube() {
+ static const GLfloat cube_vertices[] = {
+ // Vertex coordinates interleaved with color values
+ // Bottom
+ -0.5f, -0.5f, -0.5f,
+ -0.5f, -0.5f, 0.5f,
+ 0.5f, -0.5f, 0.5f,
+ 0.5f, -0.5f, -0.5f,
+ // Top
+ -0.5f, 0.5f, -0.5f,
+ -0.5f, 0.5f, 0.5f,
+ 0.5f, 0.5f, 0.5f,
+ 0.5f, 0.5f, -0.5f,
+ // Back
+ -0.5f, -0.5f, -0.5f,
+ -0.5f, 0.5f, -0.5f,
+ 0.5f, 0.5f, -0.5f,
+ 0.5f, -0.5f, -0.5f,
+ // Front
+ -0.5f, -0.5f, 0.5f,
+ -0.5f, 0.5f, 0.5f,
+ 0.5f, 0.5f, 0.5f,
+ 0.5f, -0.5f, 0.5f,
+ // Left
+ -0.5f, -0.5f, -0.5f,
+ -0.5f, -0.5f, 0.5f,
+ -0.5f, 0.5f, 0.5f,
+ -0.5f, 0.5f, -0.5f,
+ // Right
+ 0.5f, -0.5f, -0.5f,
+ 0.5f, -0.5f, 0.5f,
+ 0.5f, 0.5f, 0.5f,
+ 0.5f, 0.5f, -0.5f
+ };
+
+ static const GLfloat cube_colors[] = {
+ // Vertex coordinates interleaved with color values
+ // Bottom
+ 1.0, 0.0, 0.0,
+ 1.0, 0.0, 0.0,
+ 1.0, 0.0, 0.0,
+ 1.0, 0.0, 0.0,
+ // Top
+ 0.0, 1.0, 0.0,
+ 0.0, 1.0, 0.0,
+ 0.0, 1.0, 0.0,
+ 0.0, 1.0, 0.0,
+ // Back
+ 0.0, 0.0, 1.0,
+ 0.0, 0.0, 1.0,
+ 0.0, 0.0, 1.0,
+ 0.0, 0.0, 1.0,
+ // Front
+ 1.0, 0.0, 1.0,
+ 1.0, 0.0, 1.0,
+ 1.0, 0.0, 1.0,
+ 1.0, 0.0, 1.0,
+ // Left
+ 1.0, 1.0, 0.0,
+ 1.0, 1.0, 0.0,
+ 1.0, 1.0, 0.0,
+ 1.0, 1.0, 0.0,
+ // Right
+ 0.0, 1.0, 1.0,
+ 0.0, 1.0, 1.0,
+ 0.0, 1.0, 1.0,
+ 0.0, 1.0, 1.0
+ };
+
+ static const GLushort cube_indices[] = {
+ // Bottom
+ 0, 2, 1,
+ 0, 3, 2,
+ // Top
+ 4, 5, 6,
+ 4, 6, 7,
+ // Back
+ 8, 9, 10,
+ 8, 10, 11,
+ // Front
+ 12, 15, 14,
+ 12, 14, 13,
+ // Left
+ 16, 17, 18,
+ 16, 18, 19,
+ // Right
+ 20, 23, 22,
+ 20, 22, 21
+ };
+
+ // Generate the VBOs and upload them to the graphics context.
+ glGenBuffers(3, cube_vbos_);
+ glBindBuffer(GL_ARRAY_BUFFER, cube_vbos_[0]);
+ glBufferData(GL_ARRAY_BUFFER,
+ kVertexCount * sizeof(GLfloat) * 3,
+ cube_vertices,
+ GL_STATIC_DRAW);
+ glBindBuffer(GL_ARRAY_BUFFER, cube_vbos_[1]);
+ glBufferData(GL_ARRAY_BUFFER,
+ kVertexCount * sizeof(GLfloat) * 3,
+ cube_colors,
+ GL_STATIC_DRAW);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cube_vbos_[2]);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER,
+ kIndexCount * sizeof(GL_UNSIGNED_SHORT),
+ cube_indices,
+ GL_STATIC_DRAW);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+}
+
+void Cube::ComputeModelViewTransform(GLfloat* model_view) {
+ // This method takes into account the possiblity that |orientation_|
+ // might not be normalized.
+ double sqrx = orientation_[0] * orientation_[0];
+ double sqry = orientation_[1] * orientation_[1];
+ double sqrz = orientation_[2] * orientation_[2];
+ double sqrw = orientation_[3] * orientation_[3];
+ double sqrLength = 1.0 / (sqrx + sqry + sqrz + sqrw);
+
+ transform_4x4::LoadIdentity(model_view);
+ model_view[0] = (sqrx - sqry - sqrz + sqrw) * sqrLength;
+ model_view[5] = (-sqrx + sqry - sqrz + sqrw) * sqrLength;
+ model_view[10] = (-sqrx - sqry + sqrz + sqrw) * sqrLength;
+
+ double temp1 = orientation_[0] * orientation_[1];
+ double temp2 = orientation_[2] * orientation_[3];
+ model_view[1] = 2.0 * (temp1 + temp2) * sqrLength;
+ model_view[4] = 2.0 * (temp1 - temp2) * sqrLength;
+
+ temp1 = orientation_[0] * orientation_[2];
+ temp2 = orientation_[1] * orientation_[3];
+ model_view[2] = 2.0 * (temp1 - temp2) * sqrLength;
+ model_view[8] = 2.0 * (temp1 + temp2) * sqrLength;
+ temp1 = orientation_[1] * orientation_[2];
+ temp2 = orientation_[0] * orientation_[3];
+ model_view[6] = 2.0 * (temp1 + temp2) * sqrLength;
+ model_view[9] = 2.0 * (temp1 - temp2) * sqrLength;
+ model_view[3] = 0.0;
+ model_view[7] = 0.0;
+ model_view[11] = 0.0;
+
+ // Concatenate the translation to the eye point.
+ model_view[12] = -eye_[0];
+ model_view[13] = -eye_[1];
+ model_view[14] = -eye_[2];
+ model_view[15] = 1.0;
+}
+
+} // namespace tumbler
diff --git a/native_client_sdk/src/examples/tumbler/cube.h b/native_client_sdk/src/examples/tumbler/cube.h
new file mode 100644
index 0000000..6a992c7
--- /dev/null
+++ b/native_client_sdk/src/examples/tumbler/cube.h
@@ -0,0 +1,97 @@
+// Copyright (c) 2011 The Native Client Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXAMPLES_TUMBLER_CUBE_H_
+#define EXAMPLES_TUMBLER_CUBE_H_
+
+#include <GLES2/gl2.h>
+#include <vector>
+#include "examples/tumbler/opengl_context.h"
+#include "examples/tumbler/opengl_context_ptrs.h"
+
+namespace tumbler {
+
+// The Cube class provides a place to implement 3D rendering. It has a
+// frame that it occupies in a browser window.
+class Cube {
+ public:
+ explicit Cube(SharedOpenGLContext opengl_context);
+ ~Cube();
+
+ // Called once when a new RenderContext is first bound to the view. The
+ // bound context is guaranteed to be current and valid before calling this
+ // method.
+ void PrepareOpenGL();
+
+ // Called whenever the size of the browser view changes. This method is
+ // called at least once when the view is first made visible. Clamps the
+ // sizes to 1.
+ void Resize(int width, int height);
+
+ // Called every time the view need to be drawn. The bound context is
+ // guaranteed to be current and valid before this method is called. The
+ // visible portion of the context is flushed to the browser after this
+ // method returns.
+ void Draw();
+
+ // Accessor for width and height. To change these, call Resize.
+ const int width() const {
+ return width_;
+ }
+
+ const int height() const {
+ return height_;
+ }
+
+ // Accessor/mutator for the camera orientation.
+ void GetOrientation(std::vector<float>* orientation) const {
+ if (!orientation)
+ return;
+ (*orientation)[0] = static_cast<float>(orientation_[0]);
+ (*orientation)[1] = static_cast<float>(orientation_[1]);
+ (*orientation)[2] = static_cast<float>(orientation_[2]);
+ (*orientation)[3] = static_cast<float>(orientation_[3]);
+ }
+ void SetOrientation(const std::vector<float>& orientation) {
+ orientation_[0] = static_cast<GLfloat>(orientation[0]);
+ orientation_[1] = static_cast<GLfloat>(orientation[1]);
+ orientation_[2] = static_cast<GLfloat>(orientation[2]);
+ orientation_[3] = static_cast<GLfloat>(orientation[3]);
+ }
+
+ private:
+ // Create the shaders used to draw the cube, and link them into a program.
+ // Initializes |shader_progam_object_|, |position_loction_| and
+ // |mvp_location_|.
+ bool CreateShaders();
+
+ // Generates a cube as a series of GL_TRIANGLE_STRIPs, and initializes
+ // |index_count_| to the number of indices in the index list used as a VBO.
+ // Creates the |vbo_ids_| required for the vertex and index data and uploads
+ // the the VBO data.
+ void CreateCube();
+
+ // Build up the model-view transform from the eye and orienation properties.
+ // Assumes that |model_view| is a 4x4 matrix.
+ void ComputeModelViewTransform(GLfloat* model_view);
+
+ SharedOpenGLContext opengl_context_;
+ int width_;
+ int height_;
+ GLuint shader_program_object_; // The compiled shaders.
+ GLint position_location_; // The position attribute location.
+ GLint color_location_; // The color attribute location.
+ GLint mvp_location_; // The Model-View-Projection composite matrix.
+ GLuint cube_vbos_[3];
+ GLfloat eye_[3]; // The eye point of the virtual camera.
+ // The orientation of the virtual camera stored as a quaternion. The
+ // quaternion is laid out as {{x, y, z}, w}.
+ GLfloat orientation_[4];
+ GLfloat perspective_proj_[16];
+ GLfloat mvp_matrix_[16];
+};
+
+} // namespace tumbler
+
+#endif // EXAMPLES_TUMBLER_CUBE_H_
diff --git a/native_client_sdk/src/examples/tumbler/dragger.js b/native_client_sdk/src/examples/tumbler/dragger.js
new file mode 100644
index 0000000..232d8b5
--- /dev/null
+++ b/native_client_sdk/src/examples/tumbler/dragger.js
@@ -0,0 +1,134 @@
+// Copyright (c) 2011 The Native Client Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview This class implements a mouse-drag event. It registers for
+ * mousedown events, and when it sees one, starts capturing mousemove events
+ * until it gets a mousup event. It manufactures three drag events: the
+ * DRAG_START, DRAG and DRAG_END.
+ */
+
+// Requires bind
+
+/**
+ * Constructor for the Dragger. Register for mousedown events that happen on
+ * |opt_target|. If |opt_target| is null or undefined, then this object
+ * observes mousedown on the whole document.
+ * @param {?Element} opt_target The event target. Defaults to the whole
+ * document.
+ * @constructor
+ */
+tumbler.Dragger = function(opt_target) {
+ /**
+ * The event target.
+ * @type {Element}
+ * @private
+ */
+ this.target_ = opt_target || document;
+
+ /**
+ * The array of objects that get notified of drag events. Each object in
+ * this array get sent a handleStartDrag(), handleDrag() and handleEndDrag()
+ * message.
+ * @type {Array.<Object>}
+ * @private
+ */
+ this.listeners_ = [];
+
+ /**
+ * Flag to indicate whether the object is in a drag sequence or not.
+ * @type {boolean}
+ * @private
+ */
+ this.isDragging_ = false;
+
+ /**
+ * The function objects that get attached as event handlers. These are
+ * cached so that they can be removed on mouse up.
+ * @type {function}
+ * @private
+ */
+ this.boundMouseMove_ = null;
+ this.boundMouseUp_ = null;
+
+ this.target_.addEventListener('mousedown',
+ this.onMouseDown.bind(this),
+ false);
+}
+
+/**
+ * The ids used for drag event types.
+ * @enum {string}
+ */
+tumbler.Dragger.DragEvents = {
+ DRAG_START: 'dragstart', // Start a drag sequence
+ DRAG: 'drag', // Mouse moved during a drag sequence.
+ DRAG_END: 'dragend' // End a drag sewquence.
+};
+
+/**
+ * Add a drag listener. Each listener should respond to thhree methods:
+ * handleStartDrag(), handleDrag() and handleEndDrag(). This method assumes
+ * that |listener| does not already exist in the array of listeners.
+ * @param {!Object} listener The object that will listen to drag events.
+ */
+tumbler.Dragger.prototype.addDragListener = function(listener) {
+ this.listeners_.push(listener);
+}
+
+/**
+ * Handle a mousedown event: register for mousemove and mouseup, then tell
+ * the target that is has a DRAG_START event.
+ * @param {Event} event The mousedown event that triggered this method.
+ */
+tumbler.Dragger.prototype.onMouseDown = function(event) {
+ this.boundMouseMove_ = this.onMouseMove.bind(this);
+ this.boundMouseUp_ = this.onMouseUp.bind(this);
+ this.target_.addEventListener('mousemove', this.boundMouseMove_);
+ this.target_.addEventListener('mouseup', this.boundMouseUp_);
+ this.isDragging_ = true;
+ var dragStartEvent = { type: tumbler.Dragger.DragEvents.DRAG_START,
+ clientX: event.offsetX,
+ clientY: event.offsetY };
+ var i;
+ for (i = 0; i < this.listeners_.length; ++i) {
+ this.listeners_[i].handleStartDrag(this.target_, dragStartEvent);
+ }
+}
+
+/**
+ * Handle a mousemove event: tell the target that is has a DRAG event.
+ * @param {Event} event The mousemove event that triggered this method.
+ */
+tumbler.Dragger.prototype.onMouseMove = function(event) {
+ if (!this.isDragging_)
+ return;
+ var dragEvent = { type: tumbler.Dragger.DragEvents.DRAG,
+ clientX: event.offsetX,
+ clientY: event.offsetY};
+ var i;
+ for (i = 0; i < this.listeners_.length; ++i) {
+ this.listeners_[i].handleDrag(this.target_, dragEvent);
+ }
+}
+
+/**
+ * Handle a mouseup event: un-register for mousemove and mouseup, then tell
+ * the target that is has a DRAG_END event.
+ * @param {Event} event The mouseup event that triggered this method.
+ */
+tumbler.Dragger.prototype.onMouseUp = function(event) {
+ this.target_.removeEventListener('mouseup', this.boundMouseUp_, false);
+ this.target_.removeEventListener('mousemove', this.boundMouseMove_, false);
+ this.boundMouseUp_ = null;
+ this.boundMouseMove_ = null;
+ this.isDragging_ = false;
+ var dragEndEvent = { type: tumbler.Dragger.DragEvents.DRAG_END,
+ clientX: event.offsetX,
+ clientY: event.offsetY};
+ var i;
+ for (i = 0; i < this.listeners_.length; ++i) {
+ this.listeners_[i].handleEndDrag(this.target_, dragEndEvent);
+ }
+}
diff --git a/native_client_sdk/src/examples/tumbler/opengl_context.cc b/native_client_sdk/src/examples/tumbler/opengl_context.cc
new file mode 100644
index 0000000..f59013b
--- /dev/null
+++ b/native_client_sdk/src/examples/tumbler/opengl_context.cc
@@ -0,0 +1,84 @@
+// Copyright (c) 2011 The Native Client 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 "examples/tumbler/opengl_context.h"
+
+#include <pthread.h>
+#include "ppapi/cpp/completion_callback.h"
+#include "ppapi/gles2/gl2ext_ppapi.h"
+
+namespace {
+// This is called by the brower when the 3D context has been flushed to the
+// browser window.
+void FlushCallback(void* data, int32_t result) {
+ static_cast<tumbler::OpenGLContext*>(data)->set_flush_pending(false);
+}
+} // namespace
+
+namespace tumbler {
+
+OpenGLContext::OpenGLContext(pp::Instance* instance)
+ : pp::Graphics3DClient(instance),
+ flush_pending_(false) {
+ pp::Module* module = pp::Module::Get();
+ assert(module);
+ gles2_interface_ = static_cast<const struct PPB_OpenGLES2*>(
+ module->GetBrowserInterface(PPB_OPENGLES2_INTERFACE));
+ assert(gles2_interface_);
+}
+
+OpenGLContext::~OpenGLContext() {
+ glSetCurrentContextPPAPI(0);
+}
+
+bool OpenGLContext::MakeContextCurrent(pp::Instance* instance) {
+ if (instance == NULL) {
+ glSetCurrentContextPPAPI(0);
+ return false;
+ }
+ // Lazily create the Pepper context.
+ if (context_.is_null()) {
+ int32_t attribs[] = {
+ PP_GRAPHICS3DATTRIB_ALPHA_SIZE, 8,
+ PP_GRAPHICS3DATTRIB_DEPTH_SIZE, 24,
+ PP_GRAPHICS3DATTRIB_STENCIL_SIZE, 8,
+ PP_GRAPHICS3DATTRIB_SAMPLES, 0,
+ PP_GRAPHICS3DATTRIB_SAMPLE_BUFFERS, 0,
+ PP_GRAPHICS3DATTRIB_WIDTH, size_.width(),
+ PP_GRAPHICS3DATTRIB_HEIGHT, size_.height(),
+ PP_GRAPHICS3DATTRIB_NONE
+ };
+ context_ = pp::Graphics3D(instance, pp::Graphics3D(), attribs);
+ if (context_.is_null()) {
+ glSetCurrentContextPPAPI(0);
+ return false;
+ }
+ instance->BindGraphics(context_);
+ }
+ glSetCurrentContextPPAPI(context_.pp_resource());
+ return true;
+}
+
+void OpenGLContext::InvalidateContext(pp::Instance* instance) {
+ glSetCurrentContextPPAPI(0);
+}
+
+void OpenGLContext::ResizeContext(const pp::Size& size) {
+ size_ = size;
+ if (!context_.is_null()) {
+ context_.ResizeBuffers(size.width(), size.height());
+ }
+}
+
+
+void OpenGLContext::FlushContext() {
+ if (flush_pending()) {
+ // A flush is pending so do nothing; just drop this flush on the floor.
+ return;
+ }
+ set_flush_pending(true);
+ context_.SwapBuffers(pp::CompletionCallback(&FlushCallback, this));
+}
+} // namespace tumbler
+
diff --git a/native_client_sdk/src/examples/tumbler/opengl_context.h b/native_client_sdk/src/examples/tumbler/opengl_context.h
new file mode 100644
index 0000000..b75060d
--- /dev/null
+++ b/native_client_sdk/src/examples/tumbler/opengl_context.h
@@ -0,0 +1,93 @@
+// Copyright (c) 2011 The Native Client Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXAMPLES_TUMBLER_OPENGL_CONTEXT_H_
+#define EXAMPLES_TUMBLER_OPENGL_CONTEXT_H_
+
+///
+/// @file
+/// OpenGLContext manages the OpenGL context in the browser that is associated
+/// with a @a pp::Instance instance.
+///
+
+#include <assert.h>
+#include <pthread.h>
+
+#include <algorithm>
+#include <string>
+
+#include "examples/tumbler/opengl_context_ptrs.h"
+#include "ppapi/c/ppb_opengles2.h"
+#include "ppapi/cpp/graphics_3d_client.h"
+#include "ppapi/cpp/graphics_3d.h"
+#include "ppapi/cpp/instance.h"
+#include "ppapi/cpp/size.h"
+
+namespace tumbler {
+
+/// OpenGLContext manages an OpenGL rendering context in the browser.
+///
+class OpenGLContext : public pp::Graphics3DClient {
+ public:
+ explicit OpenGLContext(pp::Instance* instance);
+
+ /// Release all the in-browser resources used by this context, and make this
+ /// context invalid.
+ virtual ~OpenGLContext();
+
+ /// The Graphics3DClient interfcace.
+ virtual void Graphics3DContextLost() {
+ assert(!"Unexpectedly lost graphics context");
+ }
+
+ /// Make @a this the current 3D context in @a instance.
+ /// @param instance The instance of the NaCl module that will receive the
+ /// the current 3D context.
+ /// @return success.
+ bool MakeContextCurrent(pp::Instance* instance);
+
+ /// Flush the contents of this context to the browser's 3D device.
+ void FlushContext();
+
+ /// Make the underlying 3D device invalid, so that any subsequent rendering
+ /// commands will have no effect. The next call to MakeContextCurrent() will
+ /// cause the underlying 3D device to get rebound and start receiving
+ /// receiving rendering commands again. Use InvalidateContext(), for
+ /// example, when resizing the context's viewing area.
+ void InvalidateContext(pp::Instance* instance);
+
+ /// Resize the context.
+ void ResizeContext(const pp::Size& size);
+
+ /// The OpenGL ES 2.0 interface.
+ const struct PPB_OpenGLES2* gles2() const {
+ return gles2_interface_;
+ }
+
+ /// The PP_Resource needed to make GLES2 calls through the Pepper interface.
+ const PP_Resource gl_context() const {
+ return context_.pp_resource();
+ }
+
+ /// Indicate whether a flush is pending. This can only be called from the
+ /// main thread; it is not thread safe.
+ bool flush_pending() const {
+ return flush_pending_;
+ }
+ void set_flush_pending(bool flag) {
+ flush_pending_ = flag;
+ }
+
+ private:
+ pp::Size size_;
+ pp::Graphics3D context_;
+ bool flush_pending_;
+
+ const struct PPB_OpenGLES2* gles2_interface_;
+};
+
+} // namespace tumbler
+
+#endif // EXAMPLES_TUMBLER_OPENGL_CONTEXT_H_
+
diff --git a/native_client_sdk/src/examples/tumbler/opengl_context_ptrs.h b/native_client_sdk/src/examples/tumbler/opengl_context_ptrs.h
new file mode 100644
index 0000000..3478521
--- /dev/null
+++ b/native_client_sdk/src/examples/tumbler/opengl_context_ptrs.h
@@ -0,0 +1,22 @@
+// Copyright (c) 2011 The Native Client Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXAMPLES_TUMBLER_OPENGL_CONTEXT_PTRS_H_
+#define EXAMPLES_TUMBLER_OPENGL_CONTEXT_PTRS_H_
+
+// A convenience wrapper for a shared OpenGLContext pointer type. As other
+// smart pointer types are needed, add them here.
+
+#include <tr1/memory>
+
+namespace tumbler {
+
+class OpenGLContext;
+
+typedef std::tr1::shared_ptr<OpenGLContext> SharedOpenGLContext;
+
+} // namespace tumbler
+
+#endif // EXAMPLES_TUMBLER_OPENGL_CONTEXT_PTRS_H_
+
diff --git a/native_client_sdk/src/examples/tumbler/scripting_bridge.cc b/native_client_sdk/src/examples/tumbler/scripting_bridge.cc
new file mode 100644
index 0000000..e74bd9e
--- /dev/null
+++ b/native_client_sdk/src/examples/tumbler/scripting_bridge.cc
@@ -0,0 +1,95 @@
+// Copyright (c) 2011 The Native Client 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 "examples/tumbler/scripting_bridge.h"
+
+namespace {
+const char* const kWhiteSpaceCharacters = " \t";
+
+// Helper function to pull out the next token in |token_string|. A token is
+// delimited by whitespace. Scanning begins at |*pos|, if pos goes beyond the
+// end of |token_string|, it is set to std::string::npos and an empty string
+// is returned. On return, |*pos| will point to the beginning of the next
+// token. |pos| must not be NULL.
+const std::string ScanToken(const std::string& token_string, size_t* pos) {
+ std::string token;
+ if (*pos == std::string::npos) {
+ return token;
+ }
+ size_t token_start_pos = token_string.find_first_not_of(kWhiteSpaceCharacters,
+ *pos);
+ size_t token_end_pos = token_string.find_first_of(kWhiteSpaceCharacters,
+ token_start_pos);
+ if (token_start_pos != std::string::npos) {
+ token = token_string.substr(token_start_pos, token_end_pos);
+ }
+ *pos = token_end_pos;
+ return token;
+}
+
+// Take a string of the form 'name:value' and split it into two strings, one
+// containing 'name' and the other 'value'. If the ':' separator is missing,
+// or is the last character in |parameter|, |parameter| is copied to
+// |param_name|, |param_value| is left unchanged and false is returned.
+bool ParseParameter(const std::string& parameter,
+ std::string* param_name,
+ std::string* param_value) {
+ bool success = false;
+ size_t sep_pos = parameter.find_first_of(':');
+ if (sep_pos != std::string::npos) {
+ *param_name = parameter.substr(0, sep_pos);
+ if (sep_pos < parameter.length() - 1) {
+ *param_value = parameter.substr(sep_pos + 1);
+ success = true;
+ } else {
+ success = false;
+ }
+ } else {
+ *param_name = parameter;
+ success = false;
+ }
+ return success;
+}
+} // namespace
+
+namespace tumbler {
+
+bool ScriptingBridge::AddMethodNamed(const std::string& method_name,
+ SharedMethodCallbackExecutor method) {
+ if (method_name.size() == 0 || method == NULL)
+ return false;
+ method_dictionary_.insert(
+ std::pair<std::string, SharedMethodCallbackExecutor>(method_name,
+ method));
+ return true;
+}
+
+bool ScriptingBridge::InvokeMethod(const std::string& method) {
+ size_t current_pos = 0;
+ const std::string method_name = ScanToken(method, &current_pos);
+ MethodDictionary::iterator method_iter;
+ method_iter = method_dictionary_.find(method_name);
+ if (method_iter != method_dictionary_.end()) {
+ // Pull out the method parameters and build a dictionary that maps
+ // parameter names to values.
+ std::map<std::string, std::string> param_dict;
+ while (current_pos != std::string::npos) {
+ const std::string parameter = ScanToken(method, &current_pos);
+ if (parameter.length()) {
+ std::string param_name;
+ std::string param_value;
+ if (ParseParameter(parameter, &param_name, &param_value)) {
+ // Note that duplicate parameter names will override each other. The
+ // last one in the method string will be used.
+ param_dict[param_name] = param_value;
+ }
+ }
+ }
+ (*method_iter->second).Execute(*this, param_dict);
+ return true;
+ }
+ return false;
+}
+
+} // namespace tumbler
diff --git a/native_client_sdk/src/examples/tumbler/scripting_bridge.h b/native_client_sdk/src/examples/tumbler/scripting_bridge.h
new file mode 100644
index 0000000..3e2f73b
--- /dev/null
+++ b/native_client_sdk/src/examples/tumbler/scripting_bridge.h
@@ -0,0 +1,52 @@
+// Copyright (c) 2011 The Native Client Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXAMPLES_TUMBLER_SCRIPTING_BRIDGE_H_
+#define EXAMPLES_TUMBLER_SCRIPTING_BRIDGE_H_
+
+#include <map>
+#include <string>
+#include <tr1/memory>
+#include <vector>
+
+#include "examples/tumbler/callback.h"
+#include "ppapi/cpp/var.h"
+
+namespace tumbler {
+
+class MethodCallbackExecutor;
+
+// This class handles the interface between the browser and the NaCl module.
+// There is a single point of entry from the browser: postMessage(). The
+// string passed to postMessage() has this format:
+// 'function_name arg_name0:arg_0 arg_name1:arg1 ...'
+// The arguments have undetermined type; they are placed in a map of argument
+// names and values. Values are all strings, it is up to the target code to
+// do any type coercion.
+// Methods called by the scripting bridge must have a signature like this:
+// void Method(const ScriptingBridge& bridge,
+// const ParameterDictionary&);
+class ScriptingBridge {
+ public:
+ // Shared pointer type used in the method map.
+ typedef std::tr1::shared_ptr<MethodCallbackExecutor>
+ SharedMethodCallbackExecutor;
+
+ virtual ~ScriptingBridge() {}
+
+ // Causes |method_name| to be published as a method that can be called via
+ // postMessage() from the browser. Associates this method with |method|.
+ bool AddMethodNamed(const std::string& method_name,
+ SharedMethodCallbackExecutor method);
+
+ bool InvokeMethod(const std::string& method);
+
+ private:
+ typedef std::map<std::string, SharedMethodCallbackExecutor> MethodDictionary;
+
+ MethodDictionary method_dictionary_;
+};
+
+} // namespace tumbler
+#endif // EXAMPLES_TUMBLER_SCRIPTING_BRIDGE_H_
diff --git a/native_client_sdk/src/examples/tumbler/shader_util.cc b/native_client_sdk/src/examples/tumbler/shader_util.cc
new file mode 100644
index 0000000..2bbfc84
--- /dev/null
+++ b/native_client_sdk/src/examples/tumbler/shader_util.cc
@@ -0,0 +1,95 @@
+// Copyright (c) 2011 The Native Client 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 "examples/tumbler/shader_util.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+
+namespace shader_util {
+
+GLuint CreateShaderOfType(GLenum type, const char *shader_src) {
+ GLuint shader;
+ GLint compiled;
+
+ // Create the shader object
+ shader = glCreateShader(type);
+
+ if (shader == 0)
+ return 0;
+
+ // Load and compile the shader source
+ glShaderSource(shader, 1, &shader_src, NULL);
+ glCompileShader(shader);
+
+ // Check the compile status
+ glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
+ if (compiled == 0) {
+ GLint info_len = 0;
+ glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &info_len);
+ if (info_len > 1) {
+ char* info_log = reinterpret_cast<char*>(malloc(sizeof(char) * info_len));
+ glGetShaderInfoLog(shader, info_len, NULL, info_log);
+ // TODO(dspringer): We could really use a logging API.
+ printf("Error compiling shader:\n%s\n", info_log);
+ free(info_log);
+ }
+ glDeleteShader(shader);
+ return 0;
+ }
+
+ return shader;
+}
+
+GLuint CreateProgramFromVertexAndFragmentShaders(
+ const char *vertex_shader_src, const char *fragment_shader_src) {
+ GLuint vertex_shader;
+ GLuint fragment_shader;
+ GLuint program_object;
+ GLint linked;
+
+ // Load the vertex/fragment shaders
+ vertex_shader = CreateShaderOfType(GL_VERTEX_SHADER, vertex_shader_src);
+ if (vertex_shader == 0)
+ return 0;
+ fragment_shader = CreateShaderOfType(GL_FRAGMENT_SHADER, fragment_shader_src);
+ if (fragment_shader == 0) {
+ glDeleteShader(vertex_shader);
+ return 0;
+ }
+
+ // Create the program object and attach the shaders.
+ program_object = glCreateProgram();
+ if (program_object == 0)
+ return 0;
+ glAttachShader(program_object, vertex_shader);
+ glAttachShader(program_object, fragment_shader);
+
+ // Link the program
+ glLinkProgram(program_object);
+
+ // Check the link status
+ glGetProgramiv(program_object, GL_LINK_STATUS, &linked);
+ if (linked == 0) {
+ GLint info_len = 0;
+ glGetProgramiv(program_object, GL_INFO_LOG_LENGTH, &info_len);
+ if (info_len > 1) {
+ char* info_log = reinterpret_cast<char*>(malloc(info_len));
+ glGetProgramInfoLog(program_object, info_len, NULL, info_log);
+ // TODO(dspringer): We could really use a logging API.
+ printf("Error linking program:\n%s\n", info_log);
+ free(info_log);
+ }
+ glDeleteProgram(program_object);
+ return 0;
+ }
+
+ // Delete these here because they are attached to the program object.
+ glDeleteShader(vertex_shader);
+ glDeleteShader(fragment_shader);
+
+ return program_object;
+}
+
+} // namespace shader_util
diff --git a/native_client_sdk/src/examples/tumbler/shader_util.h b/native_client_sdk/src/examples/tumbler/shader_util.h
new file mode 100644
index 0000000..635b16b
--- /dev/null
+++ b/native_client_sdk/src/examples/tumbler/shader_util.h
@@ -0,0 +1,29 @@
+// Copyright (c) 2011 The Native Client Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Some simple helper functions that load shaders and create program objects.
+
+#ifndef EXAMPLES_TUMBLER_SHADER_UTIL_H_
+#define EXAMPLES_TUMBLER_SHADER_UTIL_H_
+
+#include <GLES2/gl2.h>
+
+namespace shader_util {
+
+// Load and compile a shader. |type| can be one of GL_VERTEX_SHADER or
+// GL_FRAGMENT_SHADER. Returns a non-0 value representing the compiled
+// shader on success, 0 on failure. The caller is responsible for deleting
+// the returned shader using glDeleteShader().
+GLuint CreateShaderOfType(GLenum type, const char *shader_src);
+
+// Load and compile the vertex and fragment shaders, then link these together
+// into a complete program. Returns a non-0 value representing the program on,
+// success or 0 on failure. The caller is responsible for deleting the
+// returned program using glDeleteProgram().
+GLuint CreateProgramFromVertexAndFragmentShaders(
+ const char *vertex_shader_src, const char *fragment_shader_src);
+
+} // namespace shader_util
+
+#endif // EXAMPLES_TUMBLER_SHADER_UTIL_H_
diff --git a/native_client_sdk/src/examples/tumbler/trackball.js b/native_client_sdk/src/examples/tumbler/trackball.js
new file mode 100644
index 0000000..88b9a62
--- /dev/null
+++ b/native_client_sdk/src/examples/tumbler/trackball.js
@@ -0,0 +1,296 @@
+// Copyright (c) 2011 The Native Client Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview Implement a virtual trackball in the tumbler.Trackball
+ * class. This class maps 2D mouse events to 3D rotations by simulating a
+ * trackball that you roll by dragging the mouse. There are two principle
+ * methods in the class: startAtPointInFrame which you use to begin a trackball
+ * simulation and rollToPoint, which you use while dragging the mouse. The
+ * rollToPoint method returns a rotation expressed as a quaternion.
+ */
+
+
+// Requires tumbler.Application
+// Requires tumbler.DragEvent
+// Requires tumbler.Vector3
+
+/**
+ * Constructor for the Trackball object. This class maps 2D mouse drag events
+ * into 3D rotations by simulating a trackball. The idea is to simulate
+ * clicking on the trackball, and then rolling it as you drag the mouse.
+ * The math behind the trackball is simple: start with a vector from the first
+ * mouse-click on the ball to the center of the 3D view. At the same time, set
+ * the radius of the ball to be the smaller dimension of the 3D view. As you
+ * drag the mouse around in the 3D view, a second vector is computed from the
+ * surface of the ball to the center. The axis of rotation is the cross
+ * product of these two vectors, and the angle of rotation is the angle between
+ * the two vectors.
+ * @constructor
+ */
+tumbler.Trackball = function() {
+ /**
+ * The square of the trackball's radius. The math never looks at the radius,
+ * but looks at the radius squared.
+ * @type {number}
+ * @private
+ */
+ this.sqrRadius_ = 0;
+
+ /**
+ * The 3D vector representing the point on the trackball where the mouse
+ * was clicked. Default is pointing stright through the center of the ball.
+ * @type {Object}
+ * @private
+ */
+ this.rollStart_ = new tumbler.Vector3(0, 0, 1);
+
+ /**
+ * The 2D center of the frame that encloses the trackball.
+ * @type {!Object}
+ * @private
+ */
+ this.center_ = { x: 0, y: 0 };
+
+ /**
+ * Cached camera orientation. When a drag START event happens this is set to
+ * the current orientation in the calling view's plugin. The default is the
+ * identity quaternion.
+ * @type {Array.<number>}
+ * @private
+ */
+ this.cameraOrientation_ = [0, 0, 0, 1];
+};
+
+/**
+ * Compute the dimensions of the virtual trackball to fit inside |frameSize|.
+ * The radius of the trackball is set to be 1/2 of the smaller of the two frame
+ * dimensions, the center point is at the midpoint of each side.
+ * @param {!goog.math.Size} frameSize 2D-point representing the size of the
+ * element that encloses the virtual trackball.
+ * @private
+ */
+tumbler.Trackball.prototype.initInFrame_ = function(frameSize) {
+ // Compute the radius of the virtual trackball. This is 1/2 of the smaller
+ // of the frame's width and height.
+ var halfFrameSize = 0.5 * Math.min(frameSize.width, frameSize.height);
+ // Cache the square of the trackball's radius.
+ this.sqrRadius_ = halfFrameSize * halfFrameSize;
+ // Figure the center of the view.
+ this.center_.x = frameSize.width * 0.5;
+ this.center_.y = frameSize.height * 0.5;
+};
+
+/**
+ * Method to convert (by translation) a 2D client point from a coordinate space
+ * with origin in the lower-left corner of the client view to a space with
+ * origin in the center of the client view. Use this method before mapping the
+ * 2D point to he 3D tackball point (see also the projectOnTrackball_() method).
+ * Call the startAtPointInFrame before calling this method so that the
+ * |center_| property is correctly initialized.
+ * @param {!Object} clientPoint map this point to the coordinate space with
+ * origin in thecenter of the client view.
+ * @return {Object} the converted point.
+ * @private
+ */
+tumbler.Trackball.prototype.convertClientPoint_ = function(clientPoint) {
+ var difference = { x: clientPoint.x - this.center_.x,
+ y: clientPoint.y - this.center_.y }
+ return difference;
+};
+
+/**
+ * Method to map a 2D point to a 3D point on the virtual trackball that was set
+ * up using the startAtPointInFrame method. If the point lies outside of the
+ * radius of the virtual trackball, then the z-coordinate of the 3D point
+ * is set to 0.
+ * @param {!Object.<x, y>} point 2D-point in the coordinate space with origin
+ * in the center of the client view.
+ * @return {tumbler.Vector3} the 3D point on the virtual trackball.
+ * @private
+ */
+tumbler.Trackball.prototype.projectOnTrackball_ = function(point) {
+ var sqrRadius2D = point.x * point.x + point.y * point.y;
+ var zValue;
+ if (sqrRadius2D > this.sqrRadius_) {
+ // |point| lies outside the virtual trackball's sphere, so use a virtual
+ // z-value of 0. This is equivalent to clicking on the horizontal equator
+ // of the trackball.
+ zValue = 0;
+ } else {
+ // A sphere can be defined as: r^2 = x^2 + y^2 + z^2, so z =
+ // sqrt(r^2 - (x^2 + y^2)).
+ zValue = Math.sqrt(this.sqrRadius_ - sqrRadius2D);
+ }
+ var trackballPoint = new tumbler.Vector3(point.x, point.y, zValue);
+ return trackballPoint;
+};
+
+/**
+ * Method to start up the trackball. The trackball works by pretending that a
+ * ball encloses the 3D view. You roll this pretend ball with the mouse. For
+ * example, if you click on the center of the ball and move the mouse straight
+ * to the right, you roll the ball around its Y-axis. This produces a Y-axis
+ * rotation. You can click on the "edge" of the ball and roll it around
+ * in a circle to get a Z-axis rotation.
+ * @param {!Object.<x, y>} startPoint 2D-point, usually the mouse-down
+ * point.
+ * @param {!Object.<width, height>} frameSize 2D-point representing the size of
+ * the element that encloses the virtual trackball.
+ */
+tumbler.Trackball.prototype.startAtPointInFrame =
+ function(startPoint, frameSize) {
+ this.initInFrame_(frameSize);
+ // Compute the starting vector from the surface of the ball to its center.
+ this.rollStart_ = this.projectOnTrackball_(
+ this.convertClientPoint_(startPoint));
+};
+
+/**
+ * Method to roll the virtual trackball; call this in response to a mouseDrag
+ * event. Takes |dragPoint| and projects it from 2D mouse coordinates onto the
+ * virtual track ball that was set up in startAtPointInFrame method.
+ * Returns a quaternion that represents the rotation from |rollStart_| to
+ * |rollEnd_|.
+ * @param {!Object.<x, y>} dragPoint 2D-point representing the
+ * destination mouse point.
+ * @return {Array.<number>} a quaternion that represents the rotation from
+ * the point wnere the mouse was clicked on the trackball to this point.
+ * The quaternion looks like this: [[v], cos(angle/2)], where [v] is the
+ * imaginary part of the quaternion and is computed as [x, y, z] *
+ * sin(angle/2).
+ */
+tumbler.Trackball.prototype.rollToPoint = function(dragPoint) {
+ var rollTo = this.convertClientPoint_(dragPoint);
+ if ((Math.abs(this.rollStart_.x - rollTo.x) <
+ tumbler.Trackball.DOUBLE_EPSILON) &&
+ (Math.abs(this.rollStart_.y, rollTo.y) <
+ tumbler.Trackball.DOUBLE_EPSILON)) {
+ // Not enough change in the vectors to roll the ball, return the identity
+ // quaternion.
+ return [0, 0, 0, 1];
+ }
+
+ // Compute the ending vector from the surface of the ball to its center.
+ var rollEnd = this.projectOnTrackball_(rollTo);
+
+ // Take the cross product of the two vectors. r = s X e
+ var rollVector = this.rollStart_.cross(rollEnd);
+ var invStartMag = 1.0 / this.rollStart_.magnitude();
+ var invEndMag = 1.0 / rollEnd.magnitude();
+
+ // cos(a) = (s . e) / (||s|| ||e||)
+ var cosAng = this.rollStart_.dot(rollEnd) * invStartMag * invEndMag;
+ // sin(a) = ||(s X e)|| / (||s|| ||e||)
+ var sinAng = rollVector.magnitude() * invStartMag * invEndMag;
+ // Build a quaternion that represents the rotation about |rollVector|.
+ // Use atan2 for a better angle. If you use only cos or sin, you only get
+ // half the possible angles, and you can end up with rotations that flip
+ // around near the poles.
+ var rollHalfAngle = Math.atan2(sinAng, cosAng) * 0.5;
+ rollVector.normalize();
+ // The quaternion looks like this: [[v], cos(angle/2)], where [v] is the
+ // imaginary part of the quaternion and is computed as [x, y, z] *
+ // sin(angle/2).
+ rollVector.scale(Math.sin(rollHalfAngle));
+ var ballQuaternion = [rollVector.x,
+ rollVector.y,
+ rollVector.z,
+ Math.cos(rollHalfAngle)];
+ return ballQuaternion;
+};
+
+/**
+ * Handle the drag START event: grab the current camera orientation from the
+ * sending view and set up the virtual trackball.
+ * @param {!tumbler.Application} view The view controller that called this
+ * method.
+ * @param {!tumbler.DragEvent} dragStartEvent The DRAG_START event that
+ * triggered this handler.
+ */
+tumbler.Trackball.prototype.handleStartDrag =
+ function(controller, dragStartEvent) {
+ // Cache the camera orientation. The orientations from the trackball as it
+ // rolls are concatenated to this orientation and pushed back into the
+ // plugin on the other side of the JavaScript bridge.
+ controller.setCameraOrientation(this.cameraOrientation_);
+ // Invert the y-coordinate for the trackball computations.
+ var frameSize = { width: controller.offsetWidth,
+ height: controller.offsetHeight };
+ var flippedY = { x: dragStartEvent.clientX,
+ y: frameSize.height - dragStartEvent.clientY };
+ this.startAtPointInFrame(flippedY, frameSize);
+};
+
+/**
+ * Handle the drag DRAG event: concatenate the current orientation to the
+ * cached orientation. Send this final value through to the GSPlugin via the
+ * setValueForKey() method.
+ * @param {!tumbler.Application} view The view controller that called this
+ * method.
+ * @param {!tumbler.DragEvent} dragEvent The DRAG event that triggered this
+ * handler.
+ */
+tumbler.Trackball.prototype.handleDrag =
+ function(controller, dragEvent) {
+ // Flip the y-coordinate so that the 2D origin is in the lower-left corner.
+ var frameSize = { width: controller.offsetWidth,
+ height: controller.offsetHeight };
+ var flippedY = { x: dragEvent.clientX,
+ y: frameSize.height - dragEvent.clientY };
+ controller.setCameraOrientation(
+ tumbler.multQuaternions(this.rollToPoint(flippedY),
+ this.cameraOrientation_));
+};
+
+/**
+ * Handle the drag END event: get the final orientation and concatenate it to
+ * the cached orientation.
+ * @param {!tumbler.Application} view The view controller that called this
+ * method.
+ * @param {!tumbler.DragEvent} dragEndEvent The DRAG_END event that triggered
+ * this handler.
+ */
+tumbler.Trackball.prototype.handleEndDrag =
+ function(controller, dragEndEvent) {
+ // Flip the y-coordinate so that the 2D origin is in the lower-left corner.
+ var frameSize = { width: controller.offsetWidth,
+ height: controller.offsetHeight };
+ var flippedY = { x: dragEndEvent.clientX,
+ y: frameSize.height - dragEndEvent.clientY };
+ this.cameraOrientation_ = tumbler.multQuaternions(this.rollToPoint(flippedY),
+ this.cameraOrientation_);
+ controller.setCameraOrientation(this.cameraOrientation_);
+};
+
+/**
+ * A utility function to multiply two quaterions. Returns the product q0 * q1.
+ * This is effectively the same thing as concatenating the two rotations
+ * represented in each quaternion together. Note that quaternion multiplication
+ * is NOT commutative: q0 * q1 != q1 * q0.
+ * @param {!Array.<number>} q0 A 4-element array representing the first
+ * quaternion.
+ * @param {!Array.<number>} q1 A 4-element array representing the second
+ * quaternion.
+ * @return {Array.<number>} A 4-element array representing the product q0 * q1.
+ */
+tumbler.multQuaternions = function(q0, q1) {
+ // Return q0 * q1 (note the order).
+ var qMult = [
+ q0[3] * q1[0] + q0[0] * q1[3] + q0[1] * q1[2] - q0[2] * q1[1],
+ q0[3] * q1[1] - q0[0] * q1[2] + q0[1] * q1[3] + q0[2] * q1[0],
+ q0[3] * q1[2] + q0[0] * q1[1] - q0[1] * q1[0] + q0[2] * q1[3],
+ q0[3] * q1[3] - q0[0] * q1[0] - q0[1] * q1[1] - q0[2] * q1[2]
+ ];
+ return qMult;
+};
+
+/**
+ * Real numbers that are less than this distance apart are considered
+ * equivalent.
+ * TODO(dspringer): It seems as though there should be a const like this
+ * in Closure somewhere (goog.math?).
+ * @type {number}
+ */
+tumbler.Trackball.DOUBLE_EPSILON = 1.0e-16;
diff --git a/native_client_sdk/src/examples/tumbler/transforms.cc b/native_client_sdk/src/examples/tumbler/transforms.cc
new file mode 100644
index 0000000..79cb9cf
--- /dev/null
+++ b/native_client_sdk/src/examples/tumbler/transforms.cc
@@ -0,0 +1,116 @@
+// Copyright (c) 2011 The Native Client 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 "examples/tumbler/transforms.h"
+
+#include <math.h>
+#include <string.h>
+#include <GLES2/gl2.h>
+
+namespace transform_4x4 {
+
+static const GLfloat kPI = 3.1415926535897932384626433832795f;
+
+void Translate(GLfloat* m, GLfloat tx, GLfloat ty, GLfloat tz) {
+ m[12] += (m[0] * tx + m[4] * ty + m[8] * tz);
+ m[13] += (m[1] * tx + m[5] * ty + m[9] * tz);
+ m[14] += (m[2] * tx + m[6] * ty + m[10] * tz);
+ m[15] += (m[3] * tx + m[7] * ty + m[11] * tz);
+}
+
+void Frustum(GLfloat* m,
+ GLfloat left,
+ GLfloat right,
+ GLfloat bottom,
+ GLfloat top,
+ GLfloat near_z,
+ GLfloat far_z) {
+ GLfloat delta_x = right - left;
+ GLfloat delta_y = top - bottom;
+ GLfloat delta_z = far_z - near_z;
+ GLfloat frustum[16];
+
+ if ((near_z <= 0.0f) || (far_z <= 0.0f) ||
+ (delta_x <= 0.0f) || (delta_y <= 0.0f) || (delta_z <= 0.0f))
+ return;
+
+ frustum[0] = 2.0f * near_z / delta_x;
+ frustum[1] = frustum[2] = frustum[3] = 0.0f;
+
+ frustum[5] = 2.0f * near_z / delta_y;
+ frustum[4] = frustum[6] = frustum[7] = 0.0f;
+
+ frustum[8] = (right + left) / delta_x;
+ frustum[9] = (top + bottom) / delta_y;
+ frustum[10] = -(near_z + far_z) / delta_z;
+ frustum[11] = -1.0f;
+
+ frustum[14] = -2.0f * near_z * far_z / delta_z;
+ frustum[12] = frustum[13] = frustum[15] = 0.0f;
+
+ transform_4x4::Multiply(m, frustum, m);
+}
+
+
+void Perspective(GLfloat* m,
+ GLfloat fovy,
+ GLfloat aspect,
+ GLfloat near_z,
+ GLfloat far_z) {
+ GLfloat frustum_w, frustum_h;
+
+ frustum_h = tanf((fovy * 0.5f) / 180.0f * kPI) * near_z;
+ frustum_w = frustum_h * aspect;
+ transform_4x4::Frustum(m, -frustum_w, frustum_w, -frustum_h, frustum_h,
+ near_z, far_z);
+}
+
+void Multiply(GLfloat *m, GLfloat *a, GLfloat* b) {
+ GLfloat tmp[16];
+ // tmp = a . b
+ GLfloat a0, a1, a2, a3;
+ a0 = a[0];
+ a1 = a[1];
+ a2 = a[2];
+ a3 = a[3];
+ tmp[0] = a0 * b[0] + a1 * b[4] + a2 * b[8] + a3 * b[12];
+ tmp[1] = a0 * b[1] + a1 * b[5] + a2 * b[9] + a3 * b[13];
+ tmp[2] = a0 * b[2] + a1 * b[6] + a2 * b[10] + a3 * b[14];
+ tmp[3] = a0 * b[3] + a1 * b[7] + a2 * b[11] + a3 * b[15];
+
+ a0 = a[4];
+ a1 = a[5];
+ a2 = a[6];
+ a3 = a[7];
+ tmp[4] = a0 * b[0] + a1 * b[4] + a2 * b[8] + a3 * b[12];
+ tmp[5] = a0 * b[1] + a1 * b[5] + a2 * b[9] + a3 * b[13];
+ tmp[6] = a0 * b[2] + a1 * b[6] + a2 * b[10] + a3 * b[14];
+ tmp[7] = a0 * b[3] + a1 * b[7] + a2 * b[11] + a3 * b[15];
+
+ a0 = a[8];
+ a1 = a[9];
+ a2 = a[10];
+ a3 = a[11];
+ tmp[8] = a0 * b[0] + a1 * b[4] + a2 * b[8] + a3 * b[12];
+ tmp[9] = a0 * b[1] + a1 * b[5] + a2 * b[9] + a3 * b[13];
+ tmp[10] = a0 * b[2] + a1 * b[6] + a2 * b[10] + a3 * b[14];
+ tmp[11] = a0 * b[3] + a1 * b[7] + a2 * b[11] + a3 * b[15];
+
+ a0 = a[12];
+ a1 = a[13];
+ a2 = a[14];
+ a3 = a[15];
+ tmp[12] = a0 * b[0] + a1 * b[4] + a2 * b[8] + a3 * b[12];
+ tmp[13] = a0 * b[1] + a1 * b[5] + a2 * b[9] + a3 * b[13];
+ tmp[14] = a0 * b[2] + a1 * b[6] + a2 * b[10] + a3 * b[14];
+ tmp[15] = a0 * b[3] + a1 * b[7] + a2 * b[11] + a3 * b[15];
+ memcpy(m, tmp, sizeof(GLfloat) * 4 * 4);
+}
+
+void LoadIdentity(GLfloat* m) {
+ memset(m, 0, sizeof(GLfloat) * 4 * 4);
+ m[0] = m[5] = m[10] = m[15] = 1.0f;
+}
+
+} // namespace transform_4x4
diff --git a/native_client_sdk/src/examples/tumbler/transforms.h b/native_client_sdk/src/examples/tumbler/transforms.h
new file mode 100644
index 0000000..5ac3d6e
--- /dev/null
+++ b/native_client_sdk/src/examples/tumbler/transforms.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2011 The Native Client Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXAMPLES_TUMBLER_TRANSFORMS_H_
+#define EXAMPLES_TUMBLER_TRANSFORMS_H_
+
+#include <GLES2/gl2.h>
+
+// A very simple set of 4x4 matrix routines. In all these routines, the input
+// matrix is assumed to be a 4x4 of GLfloats.
+
+namespace transform_4x4 {
+
+// Pre-multply |m| with a projection transformation 4x4 matrix from a
+// truncated pyramid viewing frustum.
+void Frustum(GLfloat* m,
+ GLfloat left,
+ GLfloat right,
+ GLfloat bottom,
+ GLfloat top,
+ GLfloat near_z,
+ GLfloat far_z);
+
+// Replace |m| with the 4x4 identity matrix.
+void LoadIdentity(GLfloat* m);
+
+// |m| <- |a| . |b|. |m| can point at the same memory as either |a| or |b|.
+void Multiply(GLfloat *m, GLfloat *a, GLfloat* b);
+
+// Pre-multiply |m| with a single-point perspective matrix based on the viewing
+// frustum whose view angle is |fovy|.
+void Perspective(GLfloat* m,
+ GLfloat fovy,
+ GLfloat aspect,
+ GLfloat near_z,
+ GLfloat far_z);
+
+// Pre-multiply |m| with a matrix that represents a translation by |tx|, |ty|,
+// |tz|.
+void Translate(GLfloat* m, GLfloat tx, GLfloat ty, GLfloat tz);
+} // namespace transform_4x4
+
+#endif // EXAMPLES_TUMBLER_TRANSFORMS_H_
+
diff --git a/native_client_sdk/src/examples/tumbler/tumbler.cc b/native_client_sdk/src/examples/tumbler/tumbler.cc
new file mode 100644
index 0000000..57343c4
--- /dev/null
+++ b/native_client_sdk/src/examples/tumbler/tumbler.cc
@@ -0,0 +1,137 @@
+// Copyright (c) 2011 The Native Client 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 "examples/tumbler/tumbler.h"
+
+#include <cstdlib>
+#include <cstring>
+#include <string>
+#include <vector>
+
+#include "examples/tumbler/cube.h"
+#include "examples/tumbler/opengl_context.h"
+#include "examples/tumbler/scripting_bridge.h"
+#include "ppapi/cpp/rect.h"
+#include "ppapi/cpp/size.h"
+#include "ppapi/cpp/var.h"
+
+namespace {
+const size_t kQuaternionElementCount = 4;
+const char* const kArrayStartCharacter = "[";
+const char* const kArrayEndCharacter = "]";
+const char* const kArrayDelimiter = ",";
+
+// Return the value of parameter named |param_name| from |parameters|. If
+// |param_name| doesn't exist, then return an empty string.
+std::string GetParameterNamed(
+ const std::string& param_name,
+ const tumbler::MethodParameter& parameters) {
+ tumbler::MethodParameter::const_iterator i =
+ parameters.find(param_name);
+ if (i == parameters.end()) {
+ return "";
+ }
+ return i->second;
+}
+
+// Convert the JSON string |array| into a vector of floats. |array| is
+// expected to be a string bounded by '[' and ']', and a comma-delimited list
+// of numbers. Any errors result in the return of an empty array.
+std::vector<float> CreateArrayFromJSON(const std::string& json_array) {
+ std::vector<float> float_array;
+ size_t array_start_pos = json_array.find_first_of(kArrayStartCharacter);
+ size_t array_end_pos = json_array.find_last_of(kArrayEndCharacter);
+ if (array_start_pos == std::string::npos ||
+ array_end_pos == std::string::npos)
+ return float_array; // Malformed JSON: missing '[' or ']'.
+ // Pull out the array elements.
+ size_t token_pos = array_start_pos + 1;
+ while (token_pos < array_end_pos) {
+ float_array.push_back(strtof(json_array.data() + token_pos, NULL));
+ size_t delim_pos = json_array.find_first_of(kArrayDelimiter, token_pos);
+ if (delim_pos == std::string::npos)
+ break;
+ token_pos = delim_pos + 1;
+ }
+ return float_array;
+}
+} // namespace
+
+namespace tumbler {
+
+Tumbler::Tumbler(PP_Instance instance)
+ : pp::Instance(instance),
+ cube_(NULL) {
+}
+
+Tumbler::~Tumbler() {
+ // Destroy the cube view while GL context is current.
+ opengl_context_->MakeContextCurrent(this);
+ delete cube_;
+}
+
+bool Tumbler::Init(uint32_t /* argc */,
+ const char* /* argn */[],
+ const char* /* argv */[]) {
+ // Add all the methods to the scripting bridge.
+ ScriptingBridge::SharedMethodCallbackExecutor set_orientation_method(
+ new tumbler::MethodCallback<Tumbler>(
+ this, &Tumbler::SetCameraOrientation));
+ scripting_bridge_.AddMethodNamed("setCameraOrientation",
+ set_orientation_method);
+ return true;
+}
+
+void Tumbler::HandleMessage(const pp::Var& message) {
+ if (!message.is_string())
+ return;
+ scripting_bridge_.InvokeMethod(message.AsString());
+}
+
+void Tumbler::DidChangeView(const pp::Rect& position, const pp::Rect& clip) {
+ int cube_width = cube_ ? cube_->width() : 0;
+ int cube_height = cube_ ? cube_->height() : 0;
+ if (position.size().width() == cube_width &&
+ position.size().height() == cube_height)
+ return; // Size didn't change, no need to update anything.
+
+ if (opengl_context_ == NULL)
+ opengl_context_.reset(new OpenGLContext(this));
+ opengl_context_->InvalidateContext(this);
+ opengl_context_->ResizeContext(position.size());
+ if (!opengl_context_->MakeContextCurrent(this))
+ return;
+ if (cube_ == NULL) {
+ cube_ = new Cube(opengl_context_);
+ cube_->PrepareOpenGL();
+ }
+ cube_->Resize(position.size().width(), position.size().height());
+ DrawSelf();
+}
+
+void Tumbler::DrawSelf() {
+ if (cube_ == NULL || opengl_context_ == NULL)
+ return;
+ opengl_context_->MakeContextCurrent(this);
+ cube_->Draw();
+ opengl_context_->FlushContext();
+}
+
+void Tumbler::SetCameraOrientation(
+ const tumbler::ScriptingBridge& bridge,
+ const tumbler::MethodParameter& parameters) {
+ // |parameters| is expected to contain one object named "orientation", whose
+ // value is a JSON string that represents an array of four floats.
+ if (parameters.size() != 1 || cube_ == NULL)
+ return;
+ std::string orientation_desc = GetParameterNamed("orientation", parameters);
+ std::vector<float> orientation = CreateArrayFromJSON(orientation_desc);
+ if (orientation.size() != kQuaternionElementCount) {
+ return;
+ }
+ cube_->SetOrientation(orientation);
+ DrawSelf();
+}
+} // namespace tumbler
+
diff --git a/native_client_sdk/src/examples/tumbler/tumbler.h b/native_client_sdk/src/examples/tumbler/tumbler.h
new file mode 100644
index 0000000..1738ec3
--- /dev/null
+++ b/native_client_sdk/src/examples/tumbler/tumbler.h
@@ -0,0 +1,64 @@
+// Copyright (c) 2011 The Native Client Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXAMPLES_TUMBLER_TUMBLER_H_
+#define EXAMPLES_TUMBLER_TUMBLER_H_
+
+#include <pthread.h>
+#include <map>
+#include <vector>
+
+#include "examples/tumbler/cube.h"
+#include "examples/tumbler/opengl_context.h"
+#include "examples/tumbler/opengl_context_ptrs.h"
+#include "examples/tumbler/scripting_bridge.h"
+#include "ppapi/cpp/instance.h"
+
+namespace tumbler {
+
+class Tumbler : public pp::Instance {
+ public:
+ explicit Tumbler(PP_Instance instance);
+
+ // The dtor makes the 3D context current before deleting the cube view, then
+ // destroys the 3D context both in the module and in the browser.
+ virtual ~Tumbler();
+
+ // Called by the browser when the NaCl module is loaded and all ready to go.
+ virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]);
+
+ // Called whenever the in-browser window changes size.
+ virtual void DidChangeView(const pp::Rect& position, const pp::Rect& clip);
+
+ // Called by the browser to handle the postMessage() call in Javascript.
+ virtual void HandleMessage(const pp::Var& message);
+
+ // Bind and publish the module's methods to JavaScript.
+ void InitializeMethods(ScriptingBridge* bridge);
+
+ // Set the camera orientation to the quaternion in |args[0]|. |args| must
+ // have length at least 1; the first element is expeted to be an Array
+ // object containing 4 floating point number elements (the quaternion).
+ // This method is bound to the JavaScript "setCameraOrientation" method and
+ // is called like this:
+ // module.setCameraOrientation([0.0, 1.0, 0.0, 0.0]);
+ void SetCameraOrientation(
+ const tumbler::ScriptingBridge& bridge,
+ const tumbler::MethodParameter& parameters);
+
+ // Called to draw the contents of the module's browser area.
+ void DrawSelf();
+
+ private:
+ // Browser connectivity and scripting support.
+ ScriptingBridge scripting_bridge_;
+
+ SharedOpenGLContext opengl_context_;
+ // Wouldn't it be awesome if we had boost::scoped_ptr<>?
+ Cube* cube_;
+};
+
+} // namespace tumbler
+
+#endif // EXAMPLES_TUMBLER_TUMBLER_H_
diff --git a/native_client_sdk/src/examples/tumbler/tumbler.html b/native_client_sdk/src/examples/tumbler/tumbler.html
new file mode 100644
index 0000000..a3002da
--- /dev/null
+++ b/native_client_sdk/src/examples/tumbler/tumbler.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+ <!--
+ Copyright (c) 2011 The Native Client Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style license that can be
+ found in the LICENSE file.
+ -->
+ <head>
+ <title>Interactive Cube Example</title>
+ <script type="text/javascript">
+ // Provide the tumbler namespace
+ tumbler = {};
+ </script>
+ <script type="text/javascript" src="bind.js"></script>
+ <script type="text/javascript" src="dragger.js"></script>
+ <script type="text/javascript" src="tumbler.js"></script>
+ <script type="text/javascript" src="vector3.js"></script>
+ <script type="text/javascript" src="trackball.js"></script>
+ </head>
+ <body id="bodyId">
+ <h1>Interactive Cube Example</h1>
+ <p>
+ The Native Client module executed in this page draws a 3D cube
+ and allows you to rotate it using a virtual trackball method.
+ </p>
+ <div id="tumbler_view"></div>
+ <script type="text/javascript">
+ tumbler.application = new tumbler.Application();
+ tumbler.application.run('tumbler_view');
+ </script>
+ </body>
+</HTML>
diff --git a/native_client_sdk/src/examples/tumbler/tumbler.js b/native_client_sdk/src/examples/tumbler/tumbler.js
new file mode 100644
index 0000000..e8e42eb
--- /dev/null
+++ b/native_client_sdk/src/examples/tumbler/tumbler.js
@@ -0,0 +1,133 @@
+// Copyright (c) 2011 The Native Client Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview The tumbler Application object. This object instantiates a
+ * Trackball object and connects it to the element named |tumbler_content|.
+ * It also conditionally embeds a debuggable module or a release module into
+ * the |tumbler_content| element.
+ */
+
+// Requires tumbler
+// Requires tumbler.Dragger
+// Requires tumbler.Trackball
+
+/**
+ * Constructor for the Application class. Use the run() method to populate
+ * the object with controllers and wire up the events.
+ * @constructor
+ */
+tumbler.Application = function() {
+ /**
+ * The native module for the application. This refers to the module loaded
+ * via the <embed> tag.
+ * @type {Element}
+ * @private
+ */
+ this.module_ = null;
+
+ /**
+ * The trackball object.
+ * @type {tumbler.Trackball}
+ * @private
+ */
+ this.trackball_ = null;
+
+ /**
+ * The mouse-drag event object.
+ * @type {tumbler.Dragger}
+ * @private
+ */
+ this.dragger_ = null;
+
+ /**
+ * The function objects that get attached as event handlers. These are
+ * cached so that they can be removed when they are no longer needed.
+ * @type {function}
+ * @private
+ */
+ this.boundModuleDidLoad_ = null;
+}
+
+/**
+ * The ids used for elements in the DOM. The Tumlber Application expects these
+ * elements to exist.
+ * @enum {string}
+ * @private
+ */
+tumbler.Application.DomIds_ = {
+ MODULE: 'tumbler', // The <embed> element representing the NaCl module
+ VIEW: 'tumbler_view' // The <div> containing the NaCl element.
+}
+
+/**
+ * Called by the module loading function once the module has been loaded.
+ * @param {?Element} nativeModule The instance of the native module.
+ */
+tumbler.Application.prototype.moduleDidLoad = function() {
+ this.module_ = document.getElementById(tumbler.Application.DomIds_.MODULE);
+ // Unbind the load function.
+ this.boundModuleDidLoad_ = null;
+
+ /**
+ * Set the camera orientation property on the NaCl module.
+ * @param {Array.<number>} orientation A 4-element array representing the
+ * camera orientation as a quaternion.
+ */
+ this.module_.setCameraOrientation = function(orientation) {
+ var methodString = 'setCameraOrientation ' +
+ 'orientation:' +
+ JSON.stringify(orientation);
+ this.postMessage(methodString);
+ }
+
+ this.trackball_ = new tumbler.Trackball();
+ this.dragger_ = new tumbler.Dragger(this.module_);
+ this.dragger_.addDragListener(this.trackball_);
+}
+
+/**
+ * Asserts that cond is true; issues an alert and throws an Error otherwise.
+ * @param {bool} cond The condition.
+ * @param {String} message The error message issued if cond is false.
+ */
+tumbler.Application.prototype.assert = function(cond, message) {
+ if (!cond) {
+ message = "Assertion failed: " + message;
+ alert(message);
+ throw new Error(message);
+ }
+}
+
+/**
+ * The run() method starts and 'runs' the application. The trackball object
+ * is allocated and all the events get wired up.
+ * @param {?String} opt_contentDivName The id of a DOM element in which to
+ * embed the Native Client module. If unspecified, defaults to
+ * VIEW. The DOM element must exist.
+ */
+tumbler.Application.prototype.run = function(opt_contentDivName) {
+ contentDivName = opt_contentDivName || tumbler.Application.DomIds_.VIEW;
+ var contentDiv = document.getElementById(contentDivName);
+ this.assert(contentDiv, "Missing DOM element '" + contentDivName + "'");
+
+ // Note that the <EMBED> element is wrapped inside a <DIV>, which has a 'load'
+ // event listener attached. This method is used instead of attaching the
+ // 'load' event listener directly to the <EMBED> element to ensure that the
+ // listener is active before the NaCl module 'load' event fires.
+ this.boundModuleDidLoad_ = this.moduleDidLoad.bind(this);
+ contentDiv.addEventListener('load', this.boundModuleDidLoad_, true);
+
+ // Load the published .nexe. This includes the 'nacl' attribute which
+ // shows how to load multi-architecture modules. Each entry in the "nexes"
+ // object in the .nmf manifest file is a key-value pair: the key is the
+ // runtime ('x86-32', 'x86-64', etc.); the value is a URL for the desired
+ // NaCl module. To load the debug versions of your .nexes, set the 'nacl'
+ // attribute to the _dbg.nmf version of the manifest file.
+ contentDiv.innerHTML = '<embed id="'
+ + tumbler.Application.DomIds_.MODULE + '" '
+ + 'src=tumbler.nmf '
+ + 'type="application/x-nacl" '
+ + 'width="480" height="480" />'
+}
diff --git a/native_client_sdk/src/examples/tumbler/tumbler_module.cc b/native_client_sdk/src/examples/tumbler/tumbler_module.cc
new file mode 100644
index 0000000..932e564
--- /dev/null
+++ b/native_client_sdk/src/examples/tumbler/tumbler_module.cc
@@ -0,0 +1,45 @@
+// Copyright (c) 2011 The Native Client 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 "examples/tumbler/tumbler.h"
+#include "ppapi/cpp/instance.h"
+#include "ppapi/cpp/module.h"
+#include "ppapi/gles2/gl2ext_ppapi.h"
+
+/// The Module class. The browser calls the CreateInstance() method to create
+/// an instance of your NaCl module on the web page. The browser creates a new
+/// instance for each <embed> tag with type="application/x-nacl".
+class TumberModule : public pp::Module {
+ public:
+ TumberModule() : pp::Module() {}
+ virtual ~TumberModule() {
+ glTerminatePPAPI();
+ }
+
+ /// Called by the browser when the module is first loaded and ready to run.
+ /// This is called once per module, not once per instance of the module on
+ /// the page.
+ virtual bool Init() {
+ return glInitializePPAPI(get_browser_interface()) == GL_TRUE;
+ }
+
+ /// Create and return a Tumbler instance object.
+ /// @param[in] instance The browser-side instance.
+ /// @return the plugin-side instance.
+ virtual pp::Instance* CreateInstance(PP_Instance instance) {
+ return new tumbler::Tumbler(instance);
+ }
+};
+
+namespace pp {
+/// Factory function called by the browser when the module is first loaded.
+/// The browser keeps a singleton of this module. It calls the
+/// CreateInstance() method on the object you return to make instances. There
+/// is one instance per <embed> tag on the page. This is the main binding
+/// point for your NaCl module with the browser.
+Module* CreateModule() {
+ return new TumberModule();
+}
+} // namespace pp
+
diff --git a/native_client_sdk/src/examples/tumbler/vector3.js b/native_client_sdk/src/examples/tumbler/vector3.js
new file mode 100644
index 0000000..a79f781
--- /dev/null
+++ b/native_client_sdk/src/examples/tumbler/vector3.js
@@ -0,0 +1,91 @@
+// Copyright (c) 2011 The Native Client Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview A 3D vector class. Proviudes some utility functions on
+ * 3-dimentional vectors.
+ */
+
+// Requires tumbler
+
+/**
+ * Constructor for the Vector3 object. This class contains a 3-tuple that
+ * represents a vector in 3D space.
+ * @param {?number} opt_x The x-coordinate for this vector. If null or
+ * undefined, the x-coordinate value is set to 0.
+ * @param {?number} opt_y The y-coordinate for this vector. If null or
+ * undefined, the y-coordinate value is set to 0.
+ * @param {?number} opt_z The z-coordinate for this vector. If null or
+ * undefined, the z-coordinate value is set to 0.
+ * @constructor
+ */
+tumbler.Vector3 = function(opt_x, opt_y, opt_z) {
+ /**
+ * The vector's 3-tuple.
+ * @type {number}
+ */
+ this.x = opt_x || 0;
+ this.y = opt_y || 0;
+ this.z = opt_z || 0;
+}
+
+/**
+ * Method to return the magnitude of a Vector3.
+ * @return {number} the magnitude of the vector.
+ */
+tumbler.Vector3.prototype.magnitude = function() {
+ return Math.sqrt(this.dot(this));
+}
+
+/**
+ * Normalize the vector in-place.
+ * @return {number} the magnitude of the vector.
+ */
+tumbler.Vector3.prototype.normalize = function() {
+ var mag = this.magnitude();
+ if (mag < tumbler.Vector3.DOUBLE_EPSILON)
+ return 0.0; // |this| is equivalent to the 0-vector, don't normalize.
+ this.scale(1.0 / mag);
+ return mag;
+}
+
+/**
+ * Scale the vector in-place by |s|.
+ * @param {!number} s The scale factor.
+ */
+tumbler.Vector3.prototype.scale = function(s) {
+ this.x *= s;
+ this.y *= s;
+ this.z *= s;
+}
+
+/**
+ * Compute the dot product: |this| . v.
+ * @param {!tumbler.Vector3} v The vector to dot.
+ * @return {number} the result of |this| . v.
+ */
+tumbler.Vector3.prototype.dot = function(v) {
+ return this.x * v.x + this.y * v.y + this.z * v.z;
+}
+
+/**
+ * Compute the cross product: |this| X v.
+ * @param {!tumbler.Vector3} v The vector to cross with.
+ * @return {tumbler.Vector3} the result of |this| X v.
+ */
+tumbler.Vector3.prototype.cross = function(v) {
+ var vCross = new tumbler.Vector3(this.y * v.z - this.z * v.y,
+ this.z * v.x - this.x * v.z,
+ this.x * v.y - this.y * v.x);
+ return vCross;
+}
+
+/**
+ * Real numbers that are less than this distance apart are considered
+ * equivalent.
+ * TODO(dspringer): It seems as though there should be a const like this
+ * in generally available somewhere.
+ * @type {number}
+ */
+tumbler.Vector3.DOUBLE_EPSILON = 1.0e-16;