3124 lines
89 KiB
C++
3124 lines
89 KiB
C++
/**
|
|
* PANDA 3D SOFTWARE
|
|
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
|
*
|
|
* All use of this software is subject to the terms of the revised BSD
|
|
* license. You should have received a copy of this license along
|
|
* with this source code in a file named "LICENSE."
|
|
*
|
|
* @file ppInstance.cxx
|
|
* @author drose
|
|
* @date 2009-06-19
|
|
*/
|
|
|
|
#include "ppInstance.h"
|
|
#include "ppPandaObject.h"
|
|
#include "ppToplevelObject.h"
|
|
#include "ppBrowserObject.h"
|
|
#include "startup.h"
|
|
#include "p3d_plugin_config.h"
|
|
#include "mkdir_complete.h"
|
|
#include "parse_color.h"
|
|
#include "nppanda3d_common.h"
|
|
|
|
// We can include this header file to get the DTOOL_PLATFORM definition, even
|
|
// though we don't link with dtool.
|
|
#include "dtool_platform.h"
|
|
#include "pandaVersion.h"
|
|
|
|
#include <fstream>
|
|
#include <algorithm>
|
|
#include <string.h> // strcmp()
|
|
#include <time.h>
|
|
|
|
#ifndef _WIN32
|
|
#include <sys/select.h>
|
|
#include <sys/time.h>
|
|
#endif // _WIN32
|
|
|
|
#ifdef HAVE_X11
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#endif // HAVE_X11
|
|
|
|
|
|
PPInstance::FileDatas PPInstance::_file_datas;
|
|
|
|
/**
|
|
* Creates a new instance of a Panda3D plugin window. The create_data
|
|
* structure is supplied from NPAPI, and defines the initial parameters
|
|
* specified in the HTML document.
|
|
*/
|
|
PPInstance::
|
|
PPInstance(NPMIMEType pluginType, NPP instance, uint16_t mode,
|
|
int16_t argc, char *argn[], char *argv[], NPSavedData *saved,
|
|
P3D_window_handle_type window_handle_type,
|
|
P3D_event_type event_type) {
|
|
_p3d_inst = NULL;
|
|
|
|
_npp_instance = instance;
|
|
_npp_mode = mode;
|
|
_window_handle_type = window_handle_type;
|
|
_event_type = event_type;
|
|
_script_object = NULL;
|
|
_contents_expiration = 0;
|
|
_failed = false;
|
|
_started = false;
|
|
|
|
// Copy the tokens and save them within this object.
|
|
_tokens.reserve(argc);
|
|
for (int i = 0; i < argc; ++i) {
|
|
if (argn[i] != NULL) {
|
|
const char *v = argv[i];
|
|
if (v == NULL) {
|
|
// Firefox might give us a NULL argv[i] in some cases.
|
|
v = "";
|
|
}
|
|
|
|
// Make the token lowercase, since HTML is case-insensitive but we're
|
|
// not.
|
|
string keyword;
|
|
for (const char *p = argn[i]; *p; ++p) {
|
|
keyword += tolower(*p);
|
|
}
|
|
|
|
P3D_token token;
|
|
token._keyword = strdup(keyword.c_str());
|
|
token._value = strdup(v);
|
|
_tokens.push_back(token);
|
|
}
|
|
}
|
|
|
|
_root_dir = global_root_dir;
|
|
|
|
_got_instance_url = false;
|
|
_p3d_instance_id = 0;
|
|
|
|
// fgcolor and bgcolor are useful to know here (in case we have to draw a
|
|
// twirling icon).
|
|
|
|
// The default bgcolor is white.
|
|
_bgcolor_r = _bgcolor_g = _bgcolor_b = 0xff;
|
|
if (has_token("bgcolor")) {
|
|
int r, g, b;
|
|
if (parse_color(r, g, b, lookup_token("bgcolor"))) {
|
|
_bgcolor_r = r;
|
|
_bgcolor_g = g;
|
|
_bgcolor_b = b;
|
|
}
|
|
}
|
|
|
|
// The default fgcolor is either black or white, according to the brightness
|
|
// of the bgcolor.
|
|
if (_bgcolor_r + _bgcolor_g + _bgcolor_b > 0x80 + 0x80 + 0x80) {
|
|
_fgcolor_r = _fgcolor_g = _fgcolor_b = 0x00;
|
|
} else {
|
|
_fgcolor_r = _fgcolor_g = _fgcolor_b = 0xff;
|
|
}
|
|
if (has_token("fgcolor")) {
|
|
int r, g, b;
|
|
if (parse_color(r, g, b, lookup_token("fgcolor"))) {
|
|
_fgcolor_r = r;
|
|
_fgcolor_g = g;
|
|
_fgcolor_b = b;
|
|
}
|
|
}
|
|
|
|
_use_xembed = false;
|
|
_got_window = false;
|
|
_python_window_open = false;
|
|
#ifdef HAVE_X11
|
|
_twirl_subprocess_pid = -1;
|
|
#ifdef HAVE_GTK
|
|
_plug = NULL;
|
|
#endif // HAVE_GTK
|
|
#endif // HAVE_X11
|
|
|
|
#ifdef _WIN32
|
|
_hwnd = NULL;
|
|
_bg_brush = NULL;
|
|
#endif // _WIN32
|
|
|
|
#ifndef _WIN32
|
|
// Save the startup time to improve precision of gettimeofday(). We also
|
|
// use this to measure elapsed time from the window parameters having been
|
|
// received.
|
|
struct timeval tv;
|
|
gettimeofday(&tv, (struct timezone *)NULL);
|
|
_init_sec = tv.tv_sec;
|
|
_init_usec = tv.tv_usec;
|
|
#endif // !_WIN32
|
|
|
|
#ifdef __APPLE__
|
|
// Get the run loop in the browser thread. (CFRunLoopGetMain() is only 10.5
|
|
// or higher. Plus, the browser thread is not necessarily the "main"
|
|
// thread.)
|
|
_run_loop_main = CFRunLoopGetCurrent();
|
|
CFRetain(_run_loop_main);
|
|
_request_timer = NULL;
|
|
INIT_LOCK(_timer_lock);
|
|
|
|
// Also set up a timer to twirl the icon until the instance loads.
|
|
_twirl_timer = NULL;
|
|
CFRunLoopTimerContext timer_context;
|
|
memset(&timer_context, 0, sizeof(timer_context));
|
|
timer_context.info = this;
|
|
_twirl_timer = CFRunLoopTimerCreate
|
|
(NULL, 0, 0.1, 0, 0, st_twirl_timer_callback, &timer_context);
|
|
CFRunLoopAddTimer(_run_loop_main, _twirl_timer, kCFRunLoopCommonModes);
|
|
#endif // __APPLE__
|
|
|
|
#ifdef MACOSX_HAS_EVENT_MODELS
|
|
_got_twirl_images = false;
|
|
#endif // MACOSX_HAS_EVENT_MODELS
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
PPInstance::
|
|
~PPInstance() {
|
|
cleanup_window();
|
|
|
|
#ifdef __APPLE__
|
|
if (_twirl_timer != NULL) {
|
|
CFRunLoopTimerInvalidate(_twirl_timer);
|
|
CFRelease(_twirl_timer);
|
|
_twirl_timer = NULL;
|
|
}
|
|
if (_request_timer != NULL) {
|
|
CFRunLoopTimerInvalidate(_request_timer);
|
|
CFRelease(_request_timer);
|
|
_request_timer = NULL;
|
|
}
|
|
_run_loop_main = CFRunLoopGetCurrent();
|
|
CFRelease(_run_loop_main);
|
|
DESTROY_LOCK(_timer_lock);
|
|
#endif // __APPLE__
|
|
|
|
#ifdef MACOSX_HAS_EVENT_MODELS
|
|
osx_release_twirl_images();
|
|
#endif // MACOSX_HAS_EVENT_MODELS
|
|
|
|
if (_p3d_inst != NULL) {
|
|
P3D_instance_finish_ptr(_p3d_inst);
|
|
_p3d_inst = NULL;
|
|
}
|
|
|
|
assert(_streams.empty());
|
|
assert(_file_datas.empty());
|
|
|
|
if (_script_object != NULL) {
|
|
browser->releaseobject(_script_object);
|
|
}
|
|
|
|
// Free the tokens we allocated.
|
|
Tokens::iterator ti;
|
|
for (ti = _tokens.begin(); ti != _tokens.end(); ++ti) {
|
|
free((char *)(*ti)._keyword);
|
|
free((char *)(*ti)._value);
|
|
}
|
|
_tokens.clear();
|
|
}
|
|
|
|
/**
|
|
* Begins the initial download of the core API. This should be called after
|
|
* constructing the PPInstance. It is a separate method than the constructor,
|
|
* because it initiates some callbacks that might rely on the object having
|
|
* been fully constructed and its pointer stored.
|
|
*/
|
|
void PPInstance::
|
|
begin() {
|
|
// On Windows and Linux, we must insist on having this call. OSX doesn't
|
|
// necessarily require it (which is lucky, since it appears that Safari
|
|
// doesn't necessarily provide it!)
|
|
#ifndef __APPLE__
|
|
if (!has_plugin_thread_async_call) {
|
|
nout << "Browser version insufficient: we require at least NPAPI version 0.19.\n";
|
|
set_failed();
|
|
}
|
|
#else
|
|
// While Safari 5 on Mac claims to provide this function, it doesn't appear
|
|
// to work. (!) So we pretend we never have it on Mac. Fortunately, this
|
|
// hack does us no harm because the _request_timer hack works fine on OSX.
|
|
has_plugin_thread_async_call = false;
|
|
#endif // __APPLE__
|
|
|
|
string url = PANDA_PACKAGE_HOST_URL;
|
|
if (!url.empty() && url[url.length() - 1] != '/') {
|
|
url += '/';
|
|
}
|
|
_download_url_prefix = url;
|
|
_standard_url_prefix = url;
|
|
nout << "Plugin is built with " << PANDA_PACKAGE_HOST_URL << "\n";
|
|
|
|
if (!is_plugin_loaded() && !_failed) {
|
|
// We need to read the contents.xml file. First, check to see if the
|
|
// version on disk is already current enough.
|
|
bool success = false;
|
|
|
|
string contents_filename = _root_dir + "/contents.xml";
|
|
if (read_contents_file(contents_filename, false)) {
|
|
if (time(NULL) < _contents_expiration) {
|
|
// Got the file, and it's good.
|
|
get_core_api();
|
|
success = true;
|
|
}
|
|
}
|
|
|
|
if (!success) {
|
|
// Go download the latest contents.xml file.
|
|
_download_url_prefix = _standard_url_prefix;
|
|
_mirrors.clear();
|
|
ostringstream strm;
|
|
strm << _download_url_prefix << "contents.xml";
|
|
|
|
// Append a uniquifying query string to the URL to force the download to
|
|
// go all the way through any caches. We use the time in seconds;
|
|
// that's unique enough.
|
|
strm << "?" << time(NULL);
|
|
url = strm.str();
|
|
|
|
PPDownloadRequest *req = new PPDownloadRequest(PPDownloadRequest::RT_contents_file);
|
|
start_download(url, req);
|
|
}
|
|
}
|
|
|
|
handle_request_loop();
|
|
}
|
|
|
|
/**
|
|
* Stores or updates the window parameters.
|
|
*/
|
|
void PPInstance::
|
|
set_window(NPWindow *window) {
|
|
if (_got_window &&
|
|
window->x == _window.x &&
|
|
window->y == _window.y &&
|
|
window->width == _window.width &&
|
|
window->height == _window.height) {
|
|
// No changes.
|
|
return;
|
|
}
|
|
|
|
if (_got_window) {
|
|
// We don't expect the browser to change the window's parent on-the-fly.
|
|
assert(_window.window == window->window);
|
|
}
|
|
|
|
if (!_got_window) {
|
|
#ifdef _WIN32
|
|
_orig_window_proc = NULL;
|
|
if (window->type == NPWindowTypeWindow) {
|
|
// Save the window handle.
|
|
_hwnd = (HWND)window->window;
|
|
|
|
// Now that we've got a window handle, we can go get the twirling icon
|
|
// images.
|
|
win_get_twirl_bitmaps();
|
|
|
|
_bg_brush = CreateSolidBrush(RGB(_bgcolor_r, _bgcolor_g, _bgcolor_b));
|
|
|
|
// Subclass the window to make it call our own window_proc instead of
|
|
// whatever window_proc it has already. This is mainly just a dopey
|
|
// trick to allow us to poll events in the main thread, but we also rely
|
|
// on this to paint the twirling icon into the browser window.
|
|
SetWindowLongPtr(_hwnd, GWLP_USERDATA, (LONG_PTR)this);
|
|
_orig_window_proc = SetWindowLongPtr(_hwnd, GWLP_WNDPROC, (LONG_PTR)st_window_proc);
|
|
|
|
// Also set a timer to go off every once in a while, to update the
|
|
// twirling icon, and also to catch events in case something slips
|
|
// through.
|
|
_init_time = GetTickCount();
|
|
SetTimer(_hwnd, 1, 100, NULL);
|
|
}
|
|
#endif // _WIN32
|
|
#ifdef MACOSX_HAS_EVENT_MODELS
|
|
osx_get_twirl_images();
|
|
#endif // MACOSX_HAS_EVENT_MODELS
|
|
}
|
|
|
|
#if defined(HAVE_GTK) && defined(HAVE_X11)
|
|
if (_use_xembed) {
|
|
if (!_got_window || _window.window != window->window) {
|
|
// The window has changed. Destroy the old GtkPlug.
|
|
if (_plug != NULL) {
|
|
gtk_widget_destroy(_plug);
|
|
_plug = NULL;
|
|
}
|
|
|
|
// Create a new GtkPlug to bind to the XEmbed socket.
|
|
_plug = gtk_plug_new((GdkNativeWindow) reinterpret_cast<XID>(window->window));
|
|
gtk_widget_show(_plug);
|
|
|
|
nout << "original XID is " << window->window << ", created X11 window "
|
|
<< GDK_DRAWABLE_XID(_plug->window) << "\n";
|
|
}
|
|
}
|
|
#endif // HAVE_GTK && HAVE_X11
|
|
|
|
_window = *window;
|
|
_got_window = true;
|
|
|
|
#ifdef HAVE_X11
|
|
if (!_failed && !_started) {
|
|
x11_start_twirl_subprocess();
|
|
}
|
|
#endif // HAVE_X11
|
|
|
|
if (!_failed) {
|
|
if (_p3d_inst == NULL) {
|
|
create_instance();
|
|
} else {
|
|
send_window();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Receives notification of a new stream object, e.g. a url request.
|
|
*/
|
|
NPError PPInstance::
|
|
new_stream(NPMIMEType type, NPStream *stream, bool seekable, uint16_t *stype) {
|
|
assert(find(_streams.begin(), _streams.end(), stream) == _streams.end());
|
|
if (_failed) {
|
|
return NPERR_GENERIC_ERROR;
|
|
}
|
|
|
|
if (stream->notifyData == NULL) {
|
|
// This is an unsolicited stream. Assume the first unsolicited stream we
|
|
// receive is the instance data; any other unsolicited stream is an error.
|
|
|
|
if (!_got_instance_url && stream->url != NULL) {
|
|
_got_instance_url = true;
|
|
_instance_url = stream->url;
|
|
stream->notifyData = new PPDownloadRequest(PPDownloadRequest::RT_instance_data);
|
|
|
|
if (_p3d_inst != NULL) {
|
|
// If we already have an instance by the time we get this stream,
|
|
// start sending the data to the instance (instead of having to mess
|
|
// around with a temporary file).
|
|
_p3d_instance_id = P3D_instance_start_stream_ptr(_p3d_inst, _instance_url.c_str());
|
|
nout << "p3d instance to stream " << _p3d_instance_id << "\n";
|
|
}
|
|
|
|
*stype = NP_NORMAL;
|
|
_streams.push_back(stream);
|
|
return NPERR_NO_ERROR;
|
|
}
|
|
|
|
// Don't finish downloading the unsolicited stream.
|
|
return NPERR_GENERIC_ERROR;
|
|
}
|
|
|
|
PPDownloadRequest *req = (PPDownloadRequest *)(stream->notifyData);
|
|
switch (req->_rtype) {
|
|
case PPDownloadRequest::RT_contents_file:
|
|
// This is the initial contents.xml file. We used to download this via
|
|
// NP_ASFILEONLY, but that option doesn't work on Windows within a Unicode
|
|
// user directory. So we use NP_NORMAL instead.
|
|
*stype = NP_NORMAL;
|
|
_streams.push_back(stream);
|
|
return NPERR_NO_ERROR;
|
|
|
|
case PPDownloadRequest::RT_core_dll:
|
|
// This is the core API DLL (or dylib or whatever). Again, we have to use
|
|
// NP_NORMAL.
|
|
*stype = NP_NORMAL;
|
|
_streams.push_back(stream);
|
|
return NPERR_NO_ERROR;
|
|
|
|
case PPDownloadRequest::RT_user:
|
|
// This is a request from the plugin. We'll receive this as a stream.
|
|
*stype = NP_NORMAL;
|
|
_streams.push_back(stream);
|
|
return NPERR_NO_ERROR;
|
|
|
|
default:
|
|
// Don't know what this is.
|
|
nout << "Unexpected request " << (int)req->_rtype << "\n";
|
|
}
|
|
|
|
return NPERR_GENERIC_ERROR;
|
|
}
|
|
|
|
|
|
/**
|
|
* Stops any download streams that are currently active on the instance. It
|
|
* is necessary to call this explicitly before destroying the instance, at
|
|
* least for Safari.
|
|
*/
|
|
void PPInstance::
|
|
stop_outstanding_streams() {
|
|
Streams::iterator si;
|
|
Streams streams;
|
|
streams.swap(_streams);
|
|
for (si = streams.begin(); si != streams.end(); ++si) {
|
|
NPStream *stream = (*si);
|
|
nout << "Stopping stream " << (void *)stream << "\n";
|
|
browser->destroystream(_npp_instance, stream, NPRES_USER_BREAK);
|
|
destroy_stream(stream, NPRES_USER_BREAK);
|
|
}
|
|
|
|
assert(_streams.empty());
|
|
|
|
// Also stop any currently pending _file_datas; these are locally-
|
|
// implemented streams.
|
|
FileDatas::iterator fi;
|
|
for (fi = _file_datas.begin(); fi != _file_datas.end(); ++fi) {
|
|
delete (*fi);
|
|
}
|
|
_file_datas.clear();
|
|
}
|
|
|
|
/**
|
|
* Called by the browser to ask how much data is ready to be received for the
|
|
* indicated stream.
|
|
*/
|
|
int32_t PPInstance::
|
|
write_ready(NPStream *stream) {
|
|
// We're supposed to return the maximum amount of data the plugin is
|
|
// prepared to handle. Gee, I don't know. As much as you can give me, I
|
|
// guess.
|
|
return 0x7fffffff;
|
|
}
|
|
|
|
/**
|
|
* Called by the browser to feed data read from a URL or whatever.
|
|
*/
|
|
int PPInstance::
|
|
write_stream(NPStream *stream, int offset, int len, void *buffer) {
|
|
if (stream->notifyData == NULL) {
|
|
nout << "Unexpected write_stream on " << stream->url << "\n";
|
|
browser->destroystream(_npp_instance, stream, NPRES_USER_BREAK);
|
|
return 0;
|
|
}
|
|
|
|
if (_failed) {
|
|
// We're done; stop this.
|
|
browser->destroystream(_npp_instance, stream, NPRES_USER_BREAK);
|
|
return 0;
|
|
}
|
|
|
|
PPDownloadRequest *req = (PPDownloadRequest *)(stream->notifyData);
|
|
switch (req->_rtype) {
|
|
case PPDownloadRequest::RT_user:
|
|
P3D_instance_feed_url_stream_ptr(_p3d_inst, req->_user_id,
|
|
P3D_RC_in_progress, 0,
|
|
stream->end, buffer, len);
|
|
return len;
|
|
|
|
case PPDownloadRequest::RT_instance_data:
|
|
// There's a special case for the RT_instance_data stream. This is the
|
|
// first, unsolicited stream that indicates the p3d instance data. We
|
|
// have to send this stream into the instance, but we can only do this
|
|
// once the instance itself has been created.
|
|
|
|
// We used to get away with returning 0 in write_ready until the instance
|
|
// was ready, but that turns out to fail under Safari Snow Leopard, which
|
|
// it seems will hold up every other download until the p3d file has been
|
|
// retrieved. Sigh. So we must start accepting the data even before the
|
|
// instance has been created, or we'll never get our contents.xml or any
|
|
// other important bits of data.
|
|
|
|
// Nowadays we solve this problem by writing the data to a temporary file
|
|
// until the instance is ready for it.
|
|
if (_p3d_inst == NULL) {
|
|
// The instance isn't ready, so stuff it in a temporary file.
|
|
if (!_p3d_temp_file.feed(stream->end, buffer, len)) {
|
|
set_failed();
|
|
}
|
|
return len;
|
|
|
|
} else {
|
|
// The instance has been created. Redirect the stream into the
|
|
// instance.
|
|
assert(!_p3d_temp_file._opened);
|
|
req->_rtype = PPDownloadRequest::RT_user;
|
|
req->_user_id = _p3d_instance_id;
|
|
P3D_instance_feed_url_stream_ptr(_p3d_inst, req->_user_id,
|
|
P3D_RC_in_progress, 0,
|
|
stream->end, buffer, len);
|
|
return len;
|
|
}
|
|
break;
|
|
|
|
case PPDownloadRequest::RT_contents_file:
|
|
if (!_contents_temp_file.feed(stream->end, buffer, len)) {
|
|
set_failed();
|
|
}
|
|
return len;
|
|
|
|
case PPDownloadRequest::RT_core_dll:
|
|
if (!_core_dll_temp_file.feed(stream->end, buffer, len)) {
|
|
set_failed();
|
|
}
|
|
return len;
|
|
|
|
default:
|
|
nout << "Unexpected write_stream on " << stream->url << "\n";
|
|
break;
|
|
}
|
|
|
|
browser->destroystream(_npp_instance, stream, NPRES_USER_BREAK);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Called by the browser to mark the end of a stream; the file has either been
|
|
* successfully downloaded or failed.
|
|
*/
|
|
NPError PPInstance::
|
|
destroy_stream(NPStream *stream, NPReason reason) {
|
|
Streams::iterator si = find(_streams.begin(), _streams.end(), stream);
|
|
if (si != _streams.end()) {
|
|
_streams.erase(si);
|
|
}
|
|
|
|
if (stream->notifyData == NULL) {
|
|
nout << "Unexpected destroy_stream on " << stream->url << "\n";
|
|
return NPERR_NO_ERROR;
|
|
}
|
|
|
|
PPDownloadRequest *req = (PPDownloadRequest *)(stream->notifyData);
|
|
|
|
P3D_result_code result_code = P3D_RC_done;
|
|
if (reason != NPRES_DONE) {
|
|
if (reason == NPRES_USER_BREAK) {
|
|
result_code = P3D_RC_shutdown;
|
|
} else {
|
|
result_code = P3D_RC_generic_error;
|
|
}
|
|
}
|
|
|
|
switch (req->_rtype) {
|
|
case PPDownloadRequest::RT_user:
|
|
if (!req->_notified_done) {
|
|
P3D_instance_feed_url_stream_ptr(_p3d_inst, req->_user_id,
|
|
result_code, 0, stream->end, NULL, 0);
|
|
req->_notified_done = true;
|
|
}
|
|
break;
|
|
|
|
case PPDownloadRequest::RT_instance_data:
|
|
if (!req->_notified_done) {
|
|
if (_p3d_inst == NULL) {
|
|
// The instance still isn't ready; just mark the data done. We'll
|
|
// send the entire file to the instance when it is ready.
|
|
_p3d_temp_file.finish();
|
|
if (result_code != P3D_RC_done) {
|
|
set_failed();
|
|
}
|
|
|
|
} else {
|
|
// The instance has (only just) been created. Tell it we've sent it
|
|
// all the data it will get.
|
|
P3D_instance_feed_url_stream_ptr(_p3d_inst, _p3d_instance_id,
|
|
result_code, 0, stream->end, NULL, 0);
|
|
}
|
|
req->_notified_done = true;
|
|
}
|
|
break;
|
|
|
|
case PPDownloadRequest::RT_contents_file:
|
|
if (!req->_notified_done) {
|
|
_contents_temp_file.finish();
|
|
if (result_code != P3D_RC_done) {
|
|
set_failed();
|
|
}
|
|
req->_notified_done = true;
|
|
}
|
|
break;
|
|
|
|
case PPDownloadRequest::RT_core_dll:
|
|
if (!req->_notified_done) {
|
|
_core_dll_temp_file.finish();
|
|
if (result_code != P3D_RC_done) {
|
|
set_failed();
|
|
}
|
|
req->_notified_done = true;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
nout << "Unexpected destroy_stream on " << stream->url << "\n";
|
|
break;
|
|
}
|
|
|
|
return NPERR_NO_ERROR;
|
|
}
|
|
|
|
/**
|
|
* Called by the browser to announce the end of a stream. This normally
|
|
* follows destroy_stream(), unless the stream was never created in the first
|
|
* place.
|
|
*/
|
|
void PPInstance::
|
|
url_notify(const char *url, NPReason reason, void *notifyData) {
|
|
if (notifyData == NULL) {
|
|
return;
|
|
}
|
|
|
|
PPDownloadRequest *req = (PPDownloadRequest *)notifyData;
|
|
if (_failed) {
|
|
// We're done; ignore this.
|
|
delete req;
|
|
return;
|
|
}
|
|
|
|
switch (req->_rtype) {
|
|
case PPDownloadRequest::RT_user:
|
|
if (!req->_notified_done) {
|
|
// We shouldn't have gotten here without notifying the stream unless the
|
|
// stream never got started (and hence we never called destroy_stream().
|
|
nout << "Failure starting stream\n";
|
|
assert(reason != NPRES_DONE);
|
|
|
|
P3D_instance_feed_url_stream_ptr(_p3d_inst, req->_user_id,
|
|
P3D_RC_generic_error, 0, 0, NULL, 0);
|
|
req->_notified_done = true;
|
|
}
|
|
break;
|
|
|
|
case PPDownloadRequest::RT_contents_file:
|
|
if (reason == NPRES_DONE) {
|
|
downloaded_contents_file(_contents_temp_file._filename);
|
|
} else {
|
|
nout << "Failure downloading " << url << "\n";
|
|
|
|
if (reason == NPRES_USER_BREAK) {
|
|
nout << "Failure due to user break\n";
|
|
} else {
|
|
// Couldn't download a fresh contents.xml for some reason. If there's
|
|
// an outstanding contents.xml file on disk, try to load that one as a
|
|
// fallback.
|
|
string contents_filename = _root_dir + "/contents.xml";
|
|
if (read_contents_file(contents_filename, false)) {
|
|
get_core_api();
|
|
} else {
|
|
nout << "Unable to read contents file " << contents_filename << "\n";
|
|
set_failed();
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case PPDownloadRequest::RT_core_dll:
|
|
if (reason == NPRES_DONE) {
|
|
downloaded_plugin(_core_dll_temp_file._filename);
|
|
|
|
} else {
|
|
nout << "Failure downloading " << url << "\n";
|
|
|
|
if (reason == NPRES_USER_BREAK) {
|
|
nout << "Failure due to user break\n";
|
|
} else {
|
|
// Couldn't download from this mirror. Try the next one.
|
|
if (!_core_urls.empty()) {
|
|
string url = _core_urls.back();
|
|
_core_urls.pop_back();
|
|
|
|
PPDownloadRequest *req2 = new PPDownloadRequest(PPDownloadRequest::RT_core_dll);
|
|
start_download(url, req2);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
nout << "Unexpected url_notify on stream type " << req->_rtype << "\n";
|
|
break;
|
|
}
|
|
|
|
delete req;
|
|
}
|
|
|
|
/**
|
|
* Called by the browser to report the filename that contains the fully-
|
|
* downloaded stream contents.
|
|
*/
|
|
void PPInstance::
|
|
stream_as_file(NPStream *stream, const char *fname) {
|
|
if (stream->notifyData == NULL) {
|
|
nout << "Unexpected stream_as_file on " << stream->url << "\n";
|
|
return;
|
|
}
|
|
|
|
string filename = fname;
|
|
#ifdef __APPLE__
|
|
// Safari seems to want to report the filename in the old-style form
|
|
// "Macintosh HD:blah:blah:blah" instead of the new-style form
|
|
// "blahblahblah". How annoying.
|
|
|
|
size_t colon = filename.find(':');
|
|
size_t slash = filename.find('/');
|
|
if (colon < slash) {
|
|
// This might be such a filename.
|
|
|
|
string fname2 = "/Volumes/";
|
|
for (size_t p = 0; p < filename.size(); ++p) {
|
|
if (filename[p] == ':') {
|
|
fname2 += '/';
|
|
} else {
|
|
fname2 += filename[p];
|
|
}
|
|
}
|
|
|
|
if (access(fname2.c_str(), R_OK) == 0) {
|
|
// Looks like we've converted it successfully.
|
|
filename = fname2;
|
|
|
|
// Here's another crazy hack. In addition to the weird filename format,
|
|
// the file that Safari tells us about appears to be a temporary file
|
|
// that Safari's about to delete. In order to protect ourselves from
|
|
// this, we need to temporarily copy the file somewhere else.
|
|
char *name = tempnam(NULL, "p3d_");
|
|
|
|
// We prefer just making a hard link; it's quick and easy.
|
|
if (link(filename.c_str(), name) != 0) {
|
|
// But sometimes the hard link might fail, particularly if these are
|
|
// two different file systems. In this case we have to open the files
|
|
// and copy the data by hand.
|
|
copy_file(filename, name);
|
|
}
|
|
|
|
filename = name;
|
|
free(name);
|
|
|
|
// TODO: remove this temporary file when we're done with it.
|
|
}
|
|
}
|
|
|
|
#endif // __APPLE__
|
|
|
|
PPDownloadRequest *req = (PPDownloadRequest *)(stream->notifyData);
|
|
downloaded_file(req, filename);
|
|
}
|
|
|
|
/**
|
|
* Handles a request from the Core API or the application, forwarding it to
|
|
* the browser as appropriate. Returns true if we should continue the request
|
|
* loop, or false to return (temporarily) to JavaScript.
|
|
*/
|
|
bool PPInstance::
|
|
handle_request(P3D_request *request) {
|
|
if (_p3d_inst == NULL || _failed) {
|
|
return false;
|
|
}
|
|
assert(request->_instance == _p3d_inst);
|
|
bool handled = false;
|
|
bool continue_loop = true;
|
|
|
|
switch (request->_request_type) {
|
|
case P3D_RT_stop:
|
|
if (_p3d_inst != NULL) {
|
|
P3D_instance_finish_ptr(_p3d_inst);
|
|
_p3d_inst = NULL;
|
|
}
|
|
cleanup_window();
|
|
// Guess the browser doesn't really care.
|
|
handled = true;
|
|
break;
|
|
|
|
case P3D_RT_get_url:
|
|
{
|
|
PPDownloadRequest *req =
|
|
new PPDownloadRequest(PPDownloadRequest::RT_user,
|
|
request->_request._get_url._unique_id);
|
|
start_download(request->_request._get_url._url, req);
|
|
}
|
|
break;
|
|
|
|
case P3D_RT_notify:
|
|
// We mostly ignore notifies, since these are handled by the core API.
|
|
// But we do check for the "onwindowopen" notify, at which point we start
|
|
// spamming the refresh requests.
|
|
if (strcmp(request->_request._notify._message, "onwindowopen") == 0) {
|
|
_python_window_open = true;
|
|
if (_got_window) {
|
|
NPRect rect = { 0, 0, (unsigned short)_window.height, (unsigned short)_window.width };
|
|
browser->invalidaterect(_npp_instance, &rect);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case P3D_RT_refresh:
|
|
if (_got_window) {
|
|
NPRect rect = { 0, 0, (unsigned short)_window.height, (unsigned short)_window.width };
|
|
browser->invalidaterect(_npp_instance, &rect);
|
|
}
|
|
break;
|
|
|
|
case P3D_RT_callback:
|
|
// In the case of a callback, yield control temporarily to JavaScript.
|
|
continue_loop = false;
|
|
break;
|
|
|
|
default:
|
|
// Some request types are not handled.
|
|
nout << "Unhandled request: " << request->_request_type << "\n";
|
|
break;
|
|
};
|
|
|
|
P3D_request_finish_ptr(request, handled);
|
|
return continue_loop;
|
|
}
|
|
|
|
/**
|
|
* This method is called from strategically-chosen browser callback functions.
|
|
* Its purpose is to provide another hook into the main thread callback,
|
|
* particularly if the PluginAsyncCall function isn't available.
|
|
*/
|
|
void PPInstance::
|
|
generic_browser_call() {
|
|
/*
|
|
if (!has_plugin_thread_async_call) {
|
|
// If we can't ask Mozilla to call us back using
|
|
// NPN_PluginThreadAsyncCall(), then we'll do it explicitly now, since we
|
|
// know we're in the main thread here.
|
|
handle_request_loop();
|
|
}
|
|
*/
|
|
}
|
|
|
|
/**
|
|
* Called by the browser as new window events are generated. Returns true if
|
|
* the event is handled, false if ignored.
|
|
*/
|
|
bool PPInstance::
|
|
handle_event(void *event) {
|
|
bool retval = false;
|
|
|
|
P3D_event_data edata;
|
|
memset(&edata, 0, sizeof(edata));
|
|
edata._event_type = _event_type;
|
|
EventAuxData aux_data;
|
|
if (_event_type == P3D_ET_osx_event_record) {
|
|
#ifdef __APPLE__
|
|
edata._event._osx_event_record._event = (EventRecord *)event;
|
|
#endif // __APPLE__
|
|
|
|
#ifdef MACOSX_HAS_EVENT_MODELS
|
|
} else if (_event_type == P3D_ET_osx_cocoa) {
|
|
// Copy the NPCocoaEvent structure componentwise into a P3DCocoaEvent
|
|
// structure.
|
|
NPCocoaEvent *np_event = (NPCocoaEvent *)event;
|
|
P3DCocoaEvent *p3d_event = &edata._event._osx_cocoa._event;
|
|
copy_cocoa_event(p3d_event, np_event, aux_data);
|
|
if (_got_window) {
|
|
handle_cocoa_event(p3d_event);
|
|
}
|
|
#endif // MACOSX_HAS_EVENT_MODELS
|
|
}
|
|
|
|
if (_p3d_inst != NULL) {
|
|
if (P3D_instance_handle_event_ptr(_p3d_inst, &edata)) {
|
|
retval = true;
|
|
}
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
/**
|
|
* Returns a toplevel object that JavaScript or whatever can read and/or
|
|
* modify to control the instance.
|
|
*/
|
|
NPObject *PPInstance::
|
|
get_panda_script_object() {
|
|
if (_script_object != NULL) {
|
|
// NPRuntime "steals" a reference to this object.
|
|
browser->retainobject(_script_object);
|
|
return _script_object;
|
|
}
|
|
|
|
P3D_object *main = NULL;
|
|
|
|
if (_p3d_inst != NULL) {
|
|
main = P3D_instance_get_panda_script_object_ptr(_p3d_inst);
|
|
}
|
|
nout << "get_panda_script_object, main = " << main << "\n";
|
|
|
|
_script_object = PPToplevelObject::make_new(this);
|
|
_script_object->set_main(main);
|
|
|
|
browser->retainobject(_script_object);
|
|
return _script_object;
|
|
}
|
|
|
|
/**
|
|
* Sets the use_xembed flag, telling the instance what kind of window object
|
|
* to expect from NPAPI. If this is true, the window object is an XID
|
|
* following the XEmbed specification; if false, it is a normal window handle.
|
|
*/
|
|
void PPInstance::
|
|
set_xembed(bool use_xembed) {
|
|
_use_xembed = use_xembed;
|
|
}
|
|
|
|
/**
|
|
* Converts the indicated P3D_object to the equivalent NPVariant, and stores
|
|
* it in result.
|
|
*/
|
|
void PPInstance::
|
|
p3dobj_to_variant(NPVariant *result, P3D_object *object) {
|
|
switch (P3D_OBJECT_GET_TYPE(object)) {
|
|
case P3D_OT_undefined:
|
|
VOID_TO_NPVARIANT(*result);
|
|
break;
|
|
|
|
case P3D_OT_none:
|
|
NULL_TO_NPVARIANT(*result);
|
|
break;
|
|
|
|
case P3D_OT_bool:
|
|
BOOLEAN_TO_NPVARIANT(P3D_OBJECT_GET_BOOL(object), *result);
|
|
break;
|
|
|
|
case P3D_OT_int:
|
|
INT32_TO_NPVARIANT(P3D_OBJECT_GET_INT(object), *result);
|
|
break;
|
|
|
|
case P3D_OT_float:
|
|
DOUBLE_TO_NPVARIANT(P3D_OBJECT_GET_FLOAT(object), *result);
|
|
break;
|
|
|
|
case P3D_OT_string:
|
|
{
|
|
int size = P3D_OBJECT_GET_STRING(object, NULL, 0);
|
|
char *buffer = (char *)browser->memalloc(size);
|
|
P3D_OBJECT_GET_STRING(object, buffer, size);
|
|
STRINGN_TO_NPVARIANT(buffer, size, *result);
|
|
}
|
|
break;
|
|
|
|
case P3D_OT_object:
|
|
{
|
|
PPPandaObject *ppobj = PPPandaObject::make_new(this, object);
|
|
OBJECT_TO_NPVARIANT(ppobj, *result);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Converts the indicated NPVariant to the equivalent P3D_object, and returns
|
|
* it (newly-allocated). The caller is responsible for freeing the returned
|
|
* object later.
|
|
*/
|
|
P3D_object *PPInstance::
|
|
variant_to_p3dobj(const NPVariant *variant) {
|
|
if (NPVARIANT_IS_VOID(*variant)) {
|
|
return P3D_new_undefined_object_ptr();
|
|
} else if (NPVARIANT_IS_NULL(*variant)) {
|
|
return P3D_new_none_object_ptr();
|
|
} else if (NPVARIANT_IS_BOOLEAN(*variant)) {
|
|
return P3D_new_bool_object_ptr(NPVARIANT_TO_BOOLEAN(*variant));
|
|
} else if (NPVARIANT_IS_INT32(*variant)) {
|
|
return P3D_new_int_object_ptr(NPVARIANT_TO_INT32(*variant));
|
|
} else if (NPVARIANT_IS_DOUBLE(*variant)) {
|
|
return P3D_new_float_object_ptr(NPVARIANT_TO_DOUBLE(*variant));
|
|
} else if (NPVARIANT_IS_STRING(*variant)) {
|
|
NPString str = NPVARIANT_TO_STRING(*variant);
|
|
const UC_NPString &uc_str = *(UC_NPString *)(&str);
|
|
return P3D_new_string_object_ptr(uc_str.UTF8Characters, uc_str.UTF8Length);
|
|
} else if (NPVARIANT_IS_OBJECT(*variant)) {
|
|
NPObject *object = NPVARIANT_TO_OBJECT(*variant);
|
|
if (object->_class == &PPPandaObject::_object_class) {
|
|
// This is really a PPPandaObject.
|
|
PPPandaObject *ppobject = (PPPandaObject *)object;
|
|
P3D_object *obj = ppobject->get_p3d_object();
|
|
return obj;
|
|
}
|
|
|
|
// It's a generic NPObject of some kind.
|
|
return new PPBrowserObject(this, object);
|
|
}
|
|
|
|
// Hmm, none of the above?
|
|
return P3D_new_none_object_ptr();
|
|
}
|
|
|
|
/**
|
|
* Outputs the variant value.
|
|
*/
|
|
void PPInstance::
|
|
output_np_variant(ostream &out, const NPVariant &result) {
|
|
if (NPVARIANT_IS_NULL(result)) {
|
|
out << "null";
|
|
} else if (NPVARIANT_IS_VOID(result)) {
|
|
out << "void";
|
|
} else if (NPVARIANT_IS_BOOLEAN(result)) {
|
|
out << "bool " << NPVARIANT_TO_BOOLEAN(result);
|
|
} else if (NPVARIANT_IS_INT32(result)) {
|
|
out << "int " << NPVARIANT_TO_INT32(result);
|
|
} else if (NPVARIANT_IS_DOUBLE(result)) {
|
|
out << "double " << NPVARIANT_TO_DOUBLE(result);
|
|
} else if (NPVARIANT_IS_STRING(result)) {
|
|
NPString str = NPVARIANT_TO_STRING(result);
|
|
const UC_NPString &uc_str = *(UC_NPString *)(&str);
|
|
out << "string " << string(uc_str.UTF8Characters, uc_str.UTF8Length);
|
|
} else if (NPVARIANT_IS_OBJECT(result)) {
|
|
NPObject *child = NPVARIANT_TO_OBJECT(result);
|
|
out << "object " << child;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Scans the <contents> element for the matching <host> element.
|
|
*/
|
|
void PPInstance::
|
|
find_host(TiXmlElement *xcontents) {
|
|
string host_url = PANDA_PACKAGE_HOST_URL;
|
|
TiXmlElement *xhost = xcontents->FirstChildElement("host");
|
|
if (xhost != NULL) {
|
|
const char *url = xhost->Attribute("url");
|
|
if (url != NULL && host_url == string(url)) {
|
|
// We're the primary host. This is the normal case.
|
|
read_xhost(xhost);
|
|
return;
|
|
|
|
} else {
|
|
// We're not the primary host; perhaps we're an alternate host.
|
|
TiXmlElement *xalthost = xhost->FirstChildElement("alt_host");
|
|
while (xalthost != NULL) {
|
|
const char *url = xalthost->Attribute("url");
|
|
if (url != NULL && host_url == string(url)) {
|
|
// Yep, we're this alternate host.
|
|
read_xhost(xhost);
|
|
return;
|
|
}
|
|
xalthost = xalthost->NextSiblingElement("alt_host");
|
|
}
|
|
}
|
|
|
|
// Hmm, didn't find the URL we used mentioned. Assume we're the primary
|
|
// host.
|
|
read_xhost(xhost);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reads the host data from the <host> (or <alt_host>) entry in the
|
|
* contents.xml file.
|
|
*/
|
|
void PPInstance::
|
|
read_xhost(TiXmlElement *xhost) {
|
|
// Get the "download" URL, which is the source from which we download
|
|
// everything other than the contents.xml file.
|
|
const char *download_url = xhost->Attribute("download_url");
|
|
if (download_url != NULL) {
|
|
_download_url_prefix = download_url;
|
|
} else {
|
|
_download_url_prefix = PANDA_PACKAGE_HOST_URL;
|
|
}
|
|
if (!_download_url_prefix.empty()) {
|
|
if (_download_url_prefix[_download_url_prefix.size() - 1] != '/') {
|
|
_download_url_prefix += "/";
|
|
}
|
|
}
|
|
|
|
TiXmlElement *xmirror = xhost->FirstChildElement("mirror");
|
|
while (xmirror != NULL) {
|
|
const char *url = xmirror->Attribute("url");
|
|
if (url != NULL) {
|
|
add_mirror(url);
|
|
}
|
|
xmirror = xmirror->NextSiblingElement("mirror");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds a new URL to serve as a mirror for this host. The mirrors will be
|
|
* consulted first, before consulting the host directly.
|
|
*/
|
|
void PPInstance::
|
|
add_mirror(string mirror_url) {
|
|
// Ensure the URL ends in a slash.
|
|
if (!mirror_url.empty() && mirror_url[mirror_url.size() - 1] != '/') {
|
|
mirror_url += '/';
|
|
}
|
|
|
|
// Add it to the _mirrors list, but only if it's not already there.
|
|
if (find(_mirrors.begin(), _mirrors.end(), mirror_url) == _mirrors.end()) {
|
|
_mirrors.push_back(mirror_url);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Selects num_mirrors elements, chosen at random, from the _mirrors list.
|
|
* Adds the selected mirrors to result. If there are fewer than num_mirrors
|
|
* elements in the list, adds only as many mirrors as we can get.
|
|
*/
|
|
void PPInstance::
|
|
choose_random_mirrors(vector<string> &result, int num_mirrors) {
|
|
vector<size_t> selected;
|
|
|
|
size_t num_to_select = min(_mirrors.size(), (size_t)num_mirrors);
|
|
while (num_to_select > 0) {
|
|
size_t i = (size_t)(((double)rand() / (double)RAND_MAX) * _mirrors.size());
|
|
while (find(selected.begin(), selected.end(), i) != selected.end()) {
|
|
// Already found this i, find a new one.
|
|
i = (size_t)(((double)rand() / (double)RAND_MAX) * _mirrors.size());
|
|
}
|
|
selected.push_back(i);
|
|
result.push_back(_mirrors[i]);
|
|
--num_to_select;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This function is attached as an asynchronous callback to each instance; it
|
|
* will be notified when the instance has a request ready. This function may
|
|
* be called in a sub-thread.
|
|
*/
|
|
void PPInstance::
|
|
request_ready(P3D_instance *instance) {
|
|
PPInstance *inst = (PPInstance *)(instance->_user_data);
|
|
assert(inst != NULL);
|
|
|
|
if (has_plugin_thread_async_call) {
|
|
#ifdef HAS_PLUGIN_THREAD_ASYNC_CALL
|
|
// Since we are running at least Gecko 1.9, and we have this very useful
|
|
// function, let's use it to ask the browser to call us back in the main
|
|
// thread.
|
|
assert((void *)browser->pluginthreadasynccall != (void *)NULL);
|
|
browser->pluginthreadasynccall(inst->_npp_instance, browser_sync_callback, NULL);
|
|
#endif // HAS_PLUGIN_THREAD_ASYNC_CALL
|
|
|
|
} else {
|
|
// If we're using an older version of Gecko, we have to do this some
|
|
// other, OS-dependent way.
|
|
|
|
#ifdef _WIN32
|
|
// Use a Windows message to forward this event to the main thread.
|
|
|
|
// Get the window handle for the window associated with this instance.
|
|
const NPWindow *win = inst->get_window();
|
|
if (win != NULL && win->type == NPWindowTypeWindow) {
|
|
PostMessage((HWND)(win->window), WM_USER, 0, 0);
|
|
}
|
|
#endif // _WIN32
|
|
|
|
#ifdef __APPLE__
|
|
// Use an OSX timer to forward this event to the main thread.
|
|
|
|
// Only set a new timer if we don't have one already started.
|
|
ACQUIRE_LOCK(inst->_timer_lock);
|
|
if (inst->_request_timer == NULL) {
|
|
CFRunLoopTimerContext timer_context;
|
|
memset(&timer_context, 0, sizeof(timer_context));
|
|
timer_context.info = inst;
|
|
inst->_request_timer = CFRunLoopTimerCreate
|
|
(NULL, 0, 0, 0, 0, timer_callback, &timer_context);
|
|
CFRunLoopAddTimer(inst->_run_loop_main, inst->_request_timer, kCFRunLoopCommonModes);
|
|
}
|
|
RELEASE_LOCK(inst->_timer_lock);
|
|
#endif // __APPLE__
|
|
|
|
// Doesn't appear to be a reliable way to simulate this in Linux.
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initiates a download request.
|
|
*/
|
|
void PPInstance::
|
|
start_download(const string &url, PPDownloadRequest *req) {
|
|
nout << "start_download: " << url << "\n";
|
|
if (url.substr(0, 7) == "file://") {
|
|
// If we're "downloading" a file URL, just go read the file directly.
|
|
downloaded_file(req, get_filename_from_url(url));
|
|
delete req;
|
|
} else {
|
|
// Otherwise, ask the browser to download it.
|
|
browser->geturlnotify(_npp_instance, url.c_str(), NULL, req);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The contents.xml file has been successfully downloaded; copy it into place.
|
|
*/
|
|
void PPInstance::
|
|
downloaded_contents_file(const string &filename) {
|
|
// Now we have the contents.xml file. Read this to get the filename and md5
|
|
// hash of our core API DLL.
|
|
if (read_contents_file(filename, true)) {
|
|
// Successfully downloaded and read, and it has been written into its
|
|
// normal place.
|
|
get_core_api();
|
|
|
|
} else {
|
|
// Error reading the contents.xml file, or in loading the core API that it
|
|
// references.
|
|
nout << "Unable to read contents file " << filename << "\n";
|
|
|
|
// If there's an outstanding contents.xml file on disk, try to load that
|
|
// one as a fallback.
|
|
string contents_filename = _root_dir + "/contents.xml";
|
|
if (read_contents_file(contents_filename, false)) {
|
|
get_core_api();
|
|
} else {
|
|
nout << "Unable to read contents file " << contents_filename << "\n";
|
|
set_failed();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attempts to open and read the contents.xml file on disk. Copies the file
|
|
* to its standard location on success. Returns true on success, false on
|
|
* failure.
|
|
*/
|
|
bool PPInstance::
|
|
read_contents_file(const string &contents_filename, bool fresh_download) {
|
|
_download_url_prefix = _standard_url_prefix;
|
|
_mirrors.clear();
|
|
|
|
TiXmlDocument doc(contents_filename.c_str());
|
|
if (!doc.LoadFile()) {
|
|
return false;
|
|
}
|
|
|
|
bool found_core_package = false;
|
|
|
|
TiXmlElement *xcontents = doc.FirstChildElement("contents");
|
|
if (xcontents != NULL) {
|
|
int max_age = P3D_CONTENTS_DEFAULT_MAX_AGE;
|
|
xcontents->Attribute("max_age", &max_age);
|
|
|
|
// Get the latest possible expiration time, based on the max_age
|
|
// indication. Any expiration time later than this is in error.
|
|
time_t now = time(NULL);
|
|
_contents_expiration = now + (time_t)max_age;
|
|
|
|
if (fresh_download) {
|
|
// Update the XML with the new download information.
|
|
TiXmlElement *xorig = xcontents->FirstChildElement("orig");
|
|
while (xorig != NULL) {
|
|
xcontents->RemoveChild(xorig);
|
|
xorig = xcontents->FirstChildElement("orig");
|
|
}
|
|
|
|
xorig = new TiXmlElement("orig");
|
|
xcontents->LinkEndChild(xorig);
|
|
|
|
xorig->SetAttribute("expiration", (int)_contents_expiration);
|
|
|
|
} else {
|
|
// Read the expiration time from the XML.
|
|
int expiration = 0;
|
|
TiXmlElement *xorig = xcontents->FirstChildElement("orig");
|
|
if (xorig != NULL) {
|
|
xorig->Attribute("expiration", &expiration);
|
|
}
|
|
|
|
_contents_expiration = min(_contents_expiration, (time_t)expiration);
|
|
}
|
|
|
|
nout << "read contents.xml, max_age = " << max_age
|
|
<< ", expires in " << max(_contents_expiration, now) - now
|
|
<< " s\n";
|
|
|
|
// Look for the <host> entry; it might point us at a different download
|
|
// URL, and it might mention some mirrors.
|
|
find_host(xcontents);
|
|
|
|
// Now look for the core API package.
|
|
_coreapi_set_ver = "";
|
|
TiXmlElement *xpackage = xcontents->FirstChildElement("package");
|
|
while (xpackage != NULL) {
|
|
const char *name = xpackage->Attribute("name");
|
|
if (name != NULL && strcmp(name, "coreapi") == 0) {
|
|
const char *platform = xpackage->Attribute("platform");
|
|
if (platform != NULL && strcmp(platform, DTOOL_PLATFORM) == 0) {
|
|
_coreapi_dll.load_xml(xpackage);
|
|
const char *set_ver = xpackage->Attribute("set_ver");
|
|
if (set_ver != NULL) {
|
|
_coreapi_set_ver = set_ver;
|
|
}
|
|
found_core_package = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
xpackage = xpackage->NextSiblingElement("package");
|
|
}
|
|
}
|
|
|
|
if (!found_core_package) {
|
|
// Couldn't find the coreapi package description.
|
|
nout << "No coreapi package defined in contents file for "
|
|
<< DTOOL_PLATFORM << "\n";
|
|
return false;
|
|
}
|
|
|
|
// Check the coreapi_set_ver token. If it is given, it specifies a minimum
|
|
// Core API version number we expect to find. If we didn't find that
|
|
// number, perhaps our contents.xml is out of date.
|
|
string coreapi_set_ver = lookup_token("coreapi_set_ver");
|
|
if (!coreapi_set_ver.empty()) {
|
|
nout << "Instance asked for Core API set_ver " << coreapi_set_ver
|
|
<< ", we found " << _coreapi_set_ver << "\n";
|
|
// But don't bother if we just freshly downloaded it.
|
|
if (!fresh_download) {
|
|
if (compare_seq(coreapi_set_ver, _coreapi_set_ver) > 0) {
|
|
// The requested set_ver value is higher than the one we have on file;
|
|
// our contents.xml file must be out of date after all.
|
|
nout << "expiring contents.xml\n";
|
|
_contents_expiration = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Success. Now save the file in its proper place.
|
|
string standard_filename = _root_dir + "/contents.xml";
|
|
|
|
mkfile_complete(standard_filename, nout);
|
|
if (!doc.SaveFile(standard_filename.c_str())) {
|
|
nout << "Couldn't rewrite " << standard_filename << "\n";
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Returns the actual filename referenced by a file: url.
|
|
*/
|
|
string PPInstance::
|
|
get_filename_from_url(const string &url) {
|
|
string filename = url.substr(7);
|
|
|
|
// Strip off a trailing query string.
|
|
size_t query = filename.find('?');
|
|
if (query != string::npos) {
|
|
filename = filename.substr(0, query);
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
// On Windows, we have to munge the filename specially, because it's been
|
|
// URL-munged. It might begin with a leading slash as well as a drive
|
|
// letter. Clean up that nonsense.
|
|
if (filename.length() >= 3 &&
|
|
(filename[0] == '/' || filename[0] == '\\') &&
|
|
isalpha(filename[1]) && filename[2] == ':') {
|
|
filename = filename.substr(1);
|
|
}
|
|
#endif // _WIN32
|
|
|
|
return filename;
|
|
}
|
|
|
|
/**
|
|
* Called to receive the fully-downloaded contents of a URL.
|
|
*/
|
|
void PPInstance::
|
|
downloaded_file(PPDownloadRequest *req, const string &filename) {
|
|
// Since we're no longer using NP_ASFILEONLY, none of these URL requests
|
|
// will normally come through this codepath (they'll go through url_notify()
|
|
// above, instead), unless we short-circuited the browser by "downloading" a
|
|
// file: url.
|
|
switch (req->_rtype) {
|
|
case PPDownloadRequest::RT_contents_file:
|
|
// The contents.xml file that gets things going.
|
|
downloaded_contents_file(filename);
|
|
break;
|
|
|
|
case PPDownloadRequest::RT_core_dll:
|
|
// This is the core API DLL (or dylib or whatever). Now that we've
|
|
// downloaded it, we can load it.
|
|
downloaded_plugin(filename);
|
|
break;
|
|
|
|
case PPDownloadRequest::RT_user:
|
|
// Here's the user-requested file. It needs to be streamed to the user,
|
|
// so we'll open the file and feed it to the user.
|
|
feed_file(req, filename);
|
|
break;
|
|
|
|
default:
|
|
// Don't know what this is.
|
|
nout << "Unexpected downloaded file, type " << (int)req->_rtype << "\n";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Opens the named file (extracted from a file:// URL) and feeds its contents
|
|
* to the core API.
|
|
*/
|
|
void PPInstance::
|
|
feed_file(PPDownloadRequest *req, const string &filename) {
|
|
StreamingFileData *file_data = new StreamingFileData(req, filename, _p3d_inst);
|
|
_file_datas.push_back(file_data);
|
|
}
|
|
|
|
/**
|
|
* Once the instance has been created, sends it all of the data we have saved
|
|
* up for it while we were waiting.
|
|
*/
|
|
void PPInstance::
|
|
send_p3d_temp_file_data() {
|
|
assert(_p3d_temp_file._opened);
|
|
|
|
nout << "Sending " << _p3d_temp_file._current_size
|
|
<< " preliminary bytes of " << _p3d_temp_file._total_size
|
|
<< " total p3d data\n";
|
|
|
|
static const size_t buffer_size = 4096;
|
|
char buffer[buffer_size];
|
|
|
|
_p3d_temp_file.close();
|
|
|
|
ifstream in(_p3d_temp_file._filename.c_str(), ios::binary);
|
|
in.read(buffer, buffer_size);
|
|
size_t total = 0;
|
|
size_t count = in.gcount();
|
|
while (count != 0) {
|
|
P3D_instance_feed_url_stream_ptr(_p3d_inst, _p3d_instance_id,
|
|
P3D_RC_in_progress, 0,
|
|
_p3d_temp_file._total_size, buffer, count);
|
|
total += count;
|
|
|
|
in.read(buffer, buffer_size);
|
|
count = in.gcount();
|
|
}
|
|
nout << "sent " << count << " bytes.\n";
|
|
|
|
in.close();
|
|
|
|
if (_p3d_temp_file._finished) {
|
|
// If we'd already finished the stream earlier, tell the plugin.
|
|
P3D_instance_feed_url_stream_ptr(_p3d_inst, _p3d_instance_id,
|
|
P3D_RC_done, 0, _p3d_temp_file._total_size,
|
|
NULL, 0);
|
|
}
|
|
_p3d_temp_file.cleanup();
|
|
}
|
|
|
|
/**
|
|
* Checks the core API DLL file against the specification in the contents
|
|
* file, and downloads it if necessary.
|
|
*/
|
|
void PPInstance::
|
|
get_core_api() {
|
|
if (_coreapi_dll.quick_verify(_root_dir)) {
|
|
// The DLL file is good. Just load it.
|
|
do_load_plugin();
|
|
|
|
} else {
|
|
// The DLL file needs to be downloaded. Build up our list of URL's to
|
|
// attempt to download it from, in reverse order.
|
|
string url;
|
|
|
|
// Our last act of desperation: hit the original host, with a query
|
|
// uniquifier, to break through any caches.
|
|
ostringstream strm;
|
|
strm << _download_url_prefix << _coreapi_dll.get_filename()
|
|
<< "?" << time(NULL);
|
|
url = strm.str();
|
|
_core_urls.push_back(url);
|
|
|
|
// Before we try that, we'll hit the original host, without a uniquifier.
|
|
url = _download_url_prefix;
|
|
url += _coreapi_dll.get_filename();
|
|
_core_urls.push_back(url);
|
|
|
|
// And before we try that, we'll try two mirrors, at random.
|
|
vector<string> mirrors;
|
|
choose_random_mirrors(mirrors, 2);
|
|
for (vector<string>::iterator si = mirrors.begin();
|
|
si != mirrors.end();
|
|
++si) {
|
|
url = (*si) + _coreapi_dll.get_filename();
|
|
_core_urls.push_back(url);
|
|
}
|
|
|
|
// Now pick the first URL off the list, and try it.
|
|
assert(!_core_urls.empty());
|
|
url = _core_urls.back();
|
|
_core_urls.pop_back();
|
|
|
|
PPDownloadRequest *req = new PPDownloadRequest(PPDownloadRequest::RT_core_dll);
|
|
start_download(url, req);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The core API DLL has been successfully downloaded; copy it into place.
|
|
*/
|
|
void PPInstance::
|
|
downloaded_plugin(const string &filename) {
|
|
// We could have been downloading this file as a stream, but that would
|
|
// cause problems with multiple instances downloading the plugin at the same
|
|
// time. Instead, we let them all download the file asfile, and then only
|
|
// one of them is allowed to copy it into place.
|
|
|
|
if (is_plugin_loaded()) {
|
|
// Some other instance got there first. Just get started.
|
|
create_instance();
|
|
return;
|
|
}
|
|
|
|
// Make sure the DLL was correctly downloaded before continuing.
|
|
if (!_coreapi_dll.quick_verify_pathname(filename)) {
|
|
nout << "After download, " << _coreapi_dll.get_filename() << " is no good.\n";
|
|
nout << "Expected:\n";
|
|
_coreapi_dll.write(nout);
|
|
const FileSpec *actual = _coreapi_dll.force_get_actual_file(filename);
|
|
if (actual != NULL) {
|
|
nout << "Found:\n";
|
|
actual->write(nout);
|
|
}
|
|
|
|
// That DLL came out wrong. Try the next URL.
|
|
if (!_core_urls.empty()) {
|
|
string url = _core_urls.back();
|
|
_core_urls.pop_back();
|
|
|
|
_core_dll_temp_file.cleanup();
|
|
PPDownloadRequest *req = new PPDownloadRequest(PPDownloadRequest::RT_core_dll);
|
|
start_download(url, req);
|
|
return;
|
|
}
|
|
|
|
set_failed();
|
|
return;
|
|
}
|
|
|
|
// Copy the file onto the target.
|
|
string pathname = _coreapi_dll.get_pathname(_root_dir);
|
|
if (!copy_file(filename, pathname)) {
|
|
nout << "Couldn't copy " << pathname << "\n";
|
|
set_failed();
|
|
return;
|
|
}
|
|
|
|
if (!_coreapi_dll.quick_verify(_root_dir)) {
|
|
nout << "After copying, " << pathname << " is no good.\n";
|
|
set_failed();
|
|
return;
|
|
}
|
|
|
|
// We downloaded and installed it successfully. Now load it.
|
|
do_load_plugin();
|
|
return;
|
|
}
|
|
|
|
|
|
/**
|
|
* Once the core API DLL has been downloaded, loads it into memory and starts
|
|
* the instance.
|
|
*/
|
|
void PPInstance::
|
|
do_load_plugin() {
|
|
string pathname = _coreapi_dll.get_pathname(_root_dir);
|
|
|
|
#ifdef P3D_PLUGIN_P3D_PLUGIN
|
|
// This is a convenience macro for development. If defined and nonempty, it
|
|
// indicates the name of the plugin DLL that we will actually run, even
|
|
// after downloading a possibly different (presumably older) version. Its
|
|
// purpose is to simplify iteration on the plugin DLL.
|
|
string override_filename = P3D_PLUGIN_P3D_PLUGIN;
|
|
if (!override_filename.empty()) {
|
|
pathname = override_filename;
|
|
}
|
|
#endif // P3D_PLUGIN_P3D_PLUGIN
|
|
|
|
nout << "Attempting to load core API from " << pathname << "\n";
|
|
string contents_filename = _root_dir + "/contents.xml";
|
|
if (!load_plugin(pathname, contents_filename, PANDA_PACKAGE_HOST_URL,
|
|
P3D_VC_normal, "", "", "", false, false,
|
|
_root_dir, "", "", nout)) {
|
|
nout << "Unable to launch core API in " << pathname << "\n";
|
|
set_failed();
|
|
return;
|
|
}
|
|
|
|
// Format the coreapi_timestamp as a string, for passing as a parameter.
|
|
ostringstream stream;
|
|
stream << _coreapi_dll.get_timestamp();
|
|
string coreapi_timestamp = stream.str();
|
|
|
|
#ifdef PANDA_OFFICIAL_VERSION
|
|
static const bool official = true;
|
|
#else
|
|
static const bool official = false;
|
|
#endif
|
|
P3D_set_plugin_version_ptr(P3D_PLUGIN_MAJOR_VERSION, P3D_PLUGIN_MINOR_VERSION,
|
|
P3D_PLUGIN_SEQUENCE_VERSION, official,
|
|
PANDA_DISTRIBUTOR,
|
|
PANDA_PACKAGE_HOST_URL, coreapi_timestamp.c_str(),
|
|
_coreapi_set_ver.c_str());
|
|
|
|
create_instance();
|
|
}
|
|
|
|
/**
|
|
* Actually creates the internal P3D_instance object, if possible and needed.
|
|
*/
|
|
void PPInstance::
|
|
create_instance() {
|
|
if (_started) {
|
|
// Already created.
|
|
return;
|
|
}
|
|
|
|
if (!is_plugin_loaded()) {
|
|
// Plugin is not loaded yet.
|
|
return;
|
|
}
|
|
|
|
#ifdef __APPLE__
|
|
// We no longer need to twirl the icon. Stop the timer.
|
|
if (_twirl_timer != NULL) {
|
|
CFRunLoopTimerInvalidate(_twirl_timer);
|
|
CFRelease(_twirl_timer);
|
|
_twirl_timer = NULL;
|
|
}
|
|
#endif // __APPLE__
|
|
|
|
#ifdef HAVE_X11
|
|
x11_stop_twirl_subprocess();
|
|
#endif // HAVE_X11
|
|
|
|
// In the Windows case, we let the timer keep running, because it also
|
|
// checks for wayward messages.
|
|
|
|
P3D_token *tokens = NULL;
|
|
if (!_tokens.empty()) {
|
|
tokens = &_tokens[0];
|
|
}
|
|
_started = true;
|
|
_p3d_inst = P3D_new_instance_ptr(request_ready, tokens, _tokens.size(),
|
|
0, NULL, this);
|
|
if (_p3d_inst == NULL) {
|
|
set_failed();
|
|
return;
|
|
}
|
|
|
|
// Now get the browser's toplevel DOM object (called the "window" object in
|
|
// JavaScript), to pass to the plugin.
|
|
NPObject *window_object = NULL;
|
|
if (browser->getvalue(_npp_instance, NPNVWindowNPObject,
|
|
&window_object) == NPERR_NO_ERROR) {
|
|
PPBrowserObject *pobj = new PPBrowserObject(this, window_object);
|
|
P3D_instance_set_browser_script_object_ptr(_p3d_inst, pobj);
|
|
browser->releaseobject(window_object);
|
|
} else {
|
|
nout << "Couldn't get window_object\n";
|
|
}
|
|
|
|
if (_script_object != NULL) {
|
|
// Now that we have a true instance, initialize our script_object with the
|
|
// proper P3D_object pointer.
|
|
P3D_object *main = P3D_instance_get_panda_script_object_ptr(_p3d_inst);
|
|
nout << "new instance, setting main = " << main << "\n";
|
|
_script_object->set_main(main);
|
|
}
|
|
|
|
if (_got_instance_url) {
|
|
// Create the user_id for streaming the p3d data into the instance.
|
|
_p3d_instance_id = P3D_instance_start_stream_ptr(_p3d_inst, _instance_url.c_str());
|
|
nout << "p3d instance to stream " << _p3d_instance_id << "\n";
|
|
|
|
// If we have already started to receive any instance data, send it to the
|
|
// plugin now.
|
|
if (_p3d_temp_file._opened) {
|
|
send_p3d_temp_file_data();
|
|
}
|
|
}
|
|
|
|
if (_got_window) {
|
|
send_window();
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Actually issues the window parameters to the internal P3D_instance object.
|
|
*/
|
|
void PPInstance::
|
|
send_window() {
|
|
assert(_p3d_inst != NULL);
|
|
|
|
int x = _window.x;
|
|
int y = _window.y;
|
|
|
|
P3D_window_handle parent_window;
|
|
memset(&parent_window, 0, sizeof(parent_window));
|
|
parent_window._window_handle_type = P3D_WHT_none;
|
|
|
|
if (_window.type == NPWindowTypeWindow) {
|
|
// We have a "windowed" plugin. Parent our window to the one we were
|
|
// given. In this case, we should also reset the offset to (0, 0), since
|
|
// the window we were given is already placed in the right spot.
|
|
#ifdef _WIN32
|
|
assert(!_use_xembed);
|
|
parent_window._window_handle_type = P3D_WHT_win_hwnd;
|
|
parent_window._handle._win_hwnd._hwnd = (HWND)(_window.window);
|
|
x = 0;
|
|
y = 0;
|
|
|
|
#elif defined(__APPLE__)
|
|
assert(!_use_xembed);
|
|
parent_window._window_handle_type = _window_handle_type;
|
|
if (_window_handle_type == P3D_WHT_osx_port) {
|
|
#if !__LP64__
|
|
NP_Port *port = (NP_Port *)_window.window;
|
|
parent_window._handle._osx_port._port = port->port;
|
|
#endif
|
|
} else if (_window_handle_type == P3D_WHT_osx_cgcontext) {
|
|
NP_CGContext *context = (NP_CGContext *)_window.window;
|
|
if (context != NULL) {
|
|
parent_window._handle._osx_cgcontext._context = context->context;
|
|
parent_window._handle._osx_cgcontext._window = (WindowRef)context->window;
|
|
}
|
|
}
|
|
|
|
#elif defined(HAVE_X11)
|
|
if (_use_xembed) {
|
|
// If we're using the XEmbed model, we've actually received an XID for a
|
|
// GtkSocket.
|
|
#ifdef HAVE_GTK
|
|
// If we're using XEmbed, pass the X11 Window pointer of our plug down
|
|
// to Panda. (Hmm, it would be nice to pass the XID object and use this
|
|
// system in general within Panda, but that's for the future, I think.)
|
|
assert(_plug != NULL);
|
|
parent_window._window_handle_type = P3D_WHT_x11_window;
|
|
parent_window._handle._x11_window._xwindow = GDK_DRAWABLE_XID(_plug->window);
|
|
#endif // HAVE_GTK
|
|
} else {
|
|
// If we're not using XEmbed, this is just a standard X11 Window
|
|
// pointer.
|
|
parent_window._window_handle_type = P3D_WHT_x11_window;
|
|
parent_window._handle._x11_window._xwindow = (X11_Window)(_window.window);
|
|
}
|
|
x = 0;
|
|
y = 0;
|
|
#endif
|
|
|
|
} else {
|
|
// We have a "windowless" plugin. Parent our window directly to the
|
|
// browser window.
|
|
#ifdef _WIN32
|
|
HWND hwnd;
|
|
if (browser->getvalue(_npp_instance, NPNVnetscapeWindow,
|
|
&hwnd) == NPERR_NO_ERROR) {
|
|
parent_window._window_handle_type = P3D_WHT_win_hwnd;
|
|
parent_window._handle._win_hwnd._hwnd = hwnd;
|
|
}
|
|
|
|
#elif defined(__APPLE__)
|
|
parent_window._window_handle_type = _window_handle_type;
|
|
if (_window_handle_type == P3D_WHT_osx_port) {
|
|
#if !__LP64__
|
|
NP_Port *port = (NP_Port *)_window.window;
|
|
parent_window._handle._osx_port._port = port->port;
|
|
#endif
|
|
} else if (_window_handle_type == P3D_WHT_osx_cgcontext) {
|
|
NP_CGContext *context = (NP_CGContext *)_window.window;
|
|
if (context != NULL) {
|
|
parent_window._handle._osx_cgcontext._context = context->context;
|
|
parent_window._handle._osx_cgcontext._window = (WindowRef)context->window;
|
|
}
|
|
}
|
|
|
|
#elif defined(HAVE_X11)
|
|
unsigned long win;
|
|
if (browser->getvalue(_npp_instance, NPNVnetscapeWindow,
|
|
&win) == NPERR_NO_ERROR) {
|
|
parent_window._window_handle_type = P3D_WHT_x11_window;
|
|
parent_window._handle._x11_window._xwindow = win;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#ifdef HAVE_X11
|
|
// In the case of X11, grab the display as well.
|
|
parent_window._handle._x11_window._xdisplay = 0;
|
|
void *disp;
|
|
if (browser->getvalue(_npp_instance, NPNVxDisplay,
|
|
&disp) == NPERR_NO_ERROR) {
|
|
parent_window._handle._x11_window._xdisplay = disp;
|
|
}
|
|
#endif
|
|
|
|
P3D_window_type window_type = P3D_WT_embedded;
|
|
if (_window.window == NULL && _event_type != P3D_ET_osx_cocoa) {
|
|
// No parent window: it must be a hidden window.
|
|
window_type = P3D_WT_hidden;
|
|
} else if (_window.width == 0 || _window.height == 0) {
|
|
// No size: hidden.
|
|
window_type = P3D_WT_hidden;
|
|
}
|
|
|
|
P3D_instance_setup_window_ptr
|
|
(_p3d_inst, window_type,
|
|
x, y, _window.width, _window.height,
|
|
&parent_window);
|
|
}
|
|
|
|
/**
|
|
* Called at instance shutdown, this restores the parent window to its
|
|
* original state.
|
|
*/
|
|
void PPInstance::
|
|
cleanup_window() {
|
|
if (_got_window) {
|
|
#ifdef _WIN32
|
|
// Restore the parent window to its own window handler.
|
|
HWND hwnd = (HWND)_window.window;
|
|
SetWindowLongPtr(hwnd, GWLP_WNDPROC, _orig_window_proc);
|
|
InvalidateRect(hwnd, NULL, true);
|
|
|
|
if (_bg_brush != NULL) {
|
|
DeleteObject(_bg_brush);
|
|
_bg_brush = NULL;
|
|
}
|
|
for (int step = 0; step < twirl_num_steps + 1; ++step) {
|
|
if (_twirl_bitmaps[step] != NULL) {
|
|
DeleteObject(_twirl_bitmaps[step]);
|
|
_twirl_bitmaps[step] = NULL;
|
|
}
|
|
}
|
|
#endif // _WIN32
|
|
|
|
#ifdef HAVE_X11
|
|
x11_stop_twirl_subprocess();
|
|
|
|
#ifdef HAVE_GTK
|
|
if (_plug != NULL) {
|
|
gtk_widget_destroy(_plug);
|
|
_plug = NULL;
|
|
}
|
|
#endif // HAVE_GTK
|
|
#endif // HAVE_X11
|
|
|
|
_got_window = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Copies the data in the file named by from_filename into the file named by
|
|
* to_filename.
|
|
*/
|
|
bool PPInstance::
|
|
copy_file(const string &from_filename, const string &to_filename) {
|
|
mkfile_complete(to_filename, nout);
|
|
ifstream in(from_filename.c_str(), ios::in | ios::binary);
|
|
ofstream out(to_filename.c_str(), ios::out | ios::binary);
|
|
|
|
static const size_t buffer_size = 4096;
|
|
char buffer[buffer_size];
|
|
|
|
in.read(buffer, buffer_size);
|
|
size_t count = in.gcount();
|
|
while (count != 0) {
|
|
out.write(buffer, count);
|
|
if (out.fail()) {
|
|
return false;
|
|
}
|
|
in.read(buffer, buffer_size);
|
|
count = in.gcount();
|
|
}
|
|
|
|
if (!in.eof()) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Returns the value associated with the first appearance of the named token,
|
|
* or empty string if the token does not appear.
|
|
*/
|
|
string PPInstance::
|
|
lookup_token(const string &keyword) const {
|
|
Tokens::const_iterator ti;
|
|
for (ti = _tokens.begin(); ti != _tokens.end(); ++ti) {
|
|
if (keyword == (*ti)._keyword) {
|
|
return (*ti)._value;
|
|
}
|
|
}
|
|
|
|
return string();
|
|
}
|
|
|
|
/**
|
|
* Returns true if the named token appears in the list, false otherwise.
|
|
*/
|
|
bool PPInstance::
|
|
has_token(const string &keyword) const {
|
|
Tokens::const_iterator ti;
|
|
for (ti = _tokens.begin(); ti != _tokens.end(); ++ti) {
|
|
if ((*ti)._keyword == keyword) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Compares the two dotted-integer sequence values numerically. Returns -1 if
|
|
* seq_a sorts first, 1 if seq_b sorts first, 0 if they are equivalent.
|
|
*/
|
|
int PPInstance::
|
|
compare_seq(const string &seq_a, const string &seq_b) {
|
|
const char *num_a = seq_a.c_str();
|
|
const char *num_b = seq_b.c_str();
|
|
int comp = compare_seq_int(num_a, num_b);
|
|
while (comp == 0) {
|
|
if (*num_a != '.') {
|
|
if (*num_b != '.') {
|
|
// Both strings ran out together.
|
|
return 0;
|
|
}
|
|
// a ran out first.
|
|
return -1;
|
|
} else if (*num_b != '.') {
|
|
// b ran out first.
|
|
return 1;
|
|
}
|
|
|
|
// Increment past the dot.
|
|
++num_a;
|
|
++num_b;
|
|
comp = compare_seq(num_a, num_b);
|
|
}
|
|
|
|
return comp;
|
|
}
|
|
|
|
/**
|
|
* Numerically compares the formatted integer value at num_a with num_b.
|
|
* Increments both num_a and num_b to the next character following the valid
|
|
* integer.
|
|
*/
|
|
int PPInstance::
|
|
compare_seq_int(const char *&num_a, const char *&num_b) {
|
|
long int a;
|
|
char *next_a;
|
|
long int b;
|
|
char *next_b;
|
|
|
|
a = strtol((char *)num_a, &next_a, 10);
|
|
b = strtol((char *)num_b, &next_b, 10);
|
|
|
|
num_a = next_a;
|
|
num_b = next_b;
|
|
|
|
if (a < b) {
|
|
return -1;
|
|
} else if (b < a) {
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called when something has gone wrong that prevents the plugin instance from
|
|
* running. Specifically, this means it failed to load the core API.
|
|
*/
|
|
void PPInstance::
|
|
set_failed() {
|
|
if (!_failed) {
|
|
_failed = true;
|
|
|
|
nout << "Plugin failed.\n";
|
|
stop_outstanding_streams();
|
|
|
|
// Look for the "onpluginfail" token.
|
|
string expression = lookup_token("onpluginfail");
|
|
|
|
if (!expression.empty()) {
|
|
// Now attempt to evaluate the expression.
|
|
NPObject *window_object = NULL;
|
|
if (browser->getvalue(_npp_instance, NPNVWindowNPObject,
|
|
&window_object) == NPERR_NO_ERROR) {
|
|
NPString npexpr = { expression.c_str(), (uint32_t)expression.length() };
|
|
NPVariant result;
|
|
if (browser->evaluate(_npp_instance, window_object,
|
|
&npexpr, &result)) {
|
|
nout << "Eval " << expression << "\n";
|
|
browser->releasevariantvalue(&result);
|
|
} else {
|
|
nout << "Unable to eval " << expression << "\n";
|
|
}
|
|
|
|
browser->releaseobject(window_object);
|
|
}
|
|
}
|
|
|
|
if (_p3d_inst != NULL) {
|
|
P3D_instance_finish_ptr(_p3d_inst);
|
|
_p3d_inst = NULL;
|
|
}
|
|
cleanup_window();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks for any new requests from the plugin, and dispatches them to the
|
|
* appropriate PPInstance. This function is called only in the main thread.
|
|
*/
|
|
void PPInstance::
|
|
handle_request_loop() {
|
|
if (!is_plugin_loaded()) {
|
|
return;
|
|
}
|
|
|
|
P3D_instance *p3d_inst = P3D_check_request_ptr(0.0);
|
|
while (p3d_inst != (P3D_instance *)NULL) {
|
|
P3D_request *request = P3D_instance_get_request_ptr(p3d_inst);
|
|
if (request != (P3D_request *)NULL) {
|
|
PPInstance *inst = (PPInstance *)(p3d_inst->_user_data);
|
|
assert(inst != NULL);
|
|
if (!inst->handle_request(request)) {
|
|
// If handling the request is meant to yield control temporarily to
|
|
// JavaScript (e.g. P3D_RT_callback), then do so now.
|
|
return;
|
|
}
|
|
|
|
if (!is_plugin_loaded()) {
|
|
// Oops, we may have unloaded the plugin as an indirect effect of
|
|
// handling the request. If so, get out of here.
|
|
return;
|
|
}
|
|
}
|
|
p3d_inst = P3D_check_request_ptr(0.0);
|
|
}
|
|
|
|
// Also check to see if we have any file_data objects that have finished and
|
|
// may be deleted.
|
|
size_t num_file_datas = _file_datas.size();
|
|
size_t i = 0;
|
|
while (i < num_file_datas) {
|
|
if (!_file_datas[i]->is_done()) {
|
|
// This one keeps going.
|
|
++i;
|
|
} else {
|
|
// This one is done.
|
|
delete _file_datas[i];
|
|
_file_datas.erase(_file_datas.begin() + i);
|
|
--num_file_datas;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This callback hook is passed to NPN_PluginThreadAsyncCall() (if that
|
|
* function is available) to forward a request to the main thread. The
|
|
* callback is actually called in the main thread.
|
|
*/
|
|
void PPInstance::
|
|
browser_sync_callback(void *) {
|
|
handle_request_loop();
|
|
}
|
|
|
|
|
|
#ifdef _WIN32
|
|
/**
|
|
* We bind this function to the parent window we were given in set_window, so
|
|
* we can spin the request_loop when needed. This is only in the Windows
|
|
* case; other platforms rely on explicit windows events.
|
|
*/
|
|
LONG PPInstance::
|
|
st_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
|
|
LONG_PTR self = GetWindowLongPtr(hwnd, GWLP_USERDATA);
|
|
if (self == NULL) {
|
|
// We haven't assigned the pointer yet (!?)
|
|
return DefWindowProc(hwnd, msg, wparam, lparam);
|
|
}
|
|
|
|
return ((PPInstance *)self)->window_proc(hwnd, msg, wparam, lparam);
|
|
}
|
|
#endif // _WIN32
|
|
|
|
#ifdef _WIN32
|
|
/**
|
|
* The non-static window_proc() function.
|
|
*
|
|
* We bind this function to the parent window we were given in set_window, so
|
|
* we can spin the request_loop when needed. This is only in the Windows
|
|
* case; other platforms rely on explicit windows events.
|
|
*/
|
|
LONG PPInstance::
|
|
window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
|
|
if (!has_plugin_thread_async_call) {
|
|
// Since we're here in the main thread, call handle_request_loop() to see
|
|
// if there are any new requests to be serviced by the main thread.
|
|
handle_request_loop();
|
|
}
|
|
|
|
switch (msg) {
|
|
case WM_ERASEBKGND:
|
|
// Eat the WM_ERASEBKGND message, so the browser's intervening window
|
|
// won't overdraw on top of our own window.
|
|
return true;
|
|
|
|
case WM_PAINT:
|
|
if (!_started) {
|
|
// If we haven't yet loaded the instance, we can paint a twirling icon
|
|
// in the window.
|
|
PAINTSTRUCT ps;
|
|
HDC dc = BeginPaint(hwnd, &ps);
|
|
win_paint_twirl(hwnd, dc);
|
|
EndPaint(hwnd, &ps);
|
|
}
|
|
return true;
|
|
|
|
case WM_TIMER:
|
|
if (!_started) {
|
|
InvalidateRect(_hwnd, NULL, FALSE);
|
|
}
|
|
break;
|
|
|
|
case WM_USER:
|
|
break;
|
|
}
|
|
|
|
return DefWindowProc(hwnd, msg, wparam, lparam);
|
|
}
|
|
#endif // _WIN32
|
|
|
|
|
|
#ifdef _WIN32
|
|
/**
|
|
* Fills _twirl_bitmaps with an array of bitmaps for drawing the twirling icon
|
|
* while we're waiting for the instance to load.
|
|
*/
|
|
void PPInstance::
|
|
win_get_twirl_bitmaps() {
|
|
BITMAPINFOHEADER bmih;
|
|
bmih.biSize = sizeof(bmih);
|
|
bmih.biWidth = twirl_width;
|
|
bmih.biHeight = -twirl_height;
|
|
bmih.biPlanes = 1;
|
|
bmih.biBitCount = 32;
|
|
bmih.biCompression = BI_RGB;
|
|
bmih.biSizeImage = 0;
|
|
bmih.biXPelsPerMeter = 0;
|
|
bmih.biYPelsPerMeter = 0;
|
|
bmih.biClrUsed = 0;
|
|
bmih.biClrImportant = 0;
|
|
|
|
BITMAPINFO bmi;
|
|
memcpy(&bmi, &bmih, sizeof(bmih));
|
|
|
|
HDC dc = GetDC(_hwnd);
|
|
|
|
static const size_t twirl_size = twirl_width * twirl_height;
|
|
unsigned char twirl_data[twirl_size * 3];
|
|
unsigned char new_data[twirl_size * 4];
|
|
|
|
for (int step = 0; step < twirl_num_steps + 1; ++step) {
|
|
get_twirl_data(twirl_data, twirl_size, step,
|
|
_fgcolor_r, _fgcolor_g, _fgcolor_b,
|
|
_bgcolor_r, _bgcolor_g, _bgcolor_b);
|
|
|
|
// Expand out the RGB channels into RGBA.
|
|
for (int yi = 0; yi < twirl_height; ++yi) {
|
|
const unsigned char *sp = twirl_data + yi * twirl_width * 3;
|
|
unsigned char *dp = new_data + yi * twirl_width * 4;
|
|
for (int xi = 0; xi < twirl_width; ++xi) {
|
|
// RGB <= BGR.
|
|
dp[0] = sp[2];
|
|
dp[1] = sp[1];
|
|
dp[2] = sp[0];
|
|
dp[3] = (unsigned char)0xff;
|
|
sp += 3;
|
|
dp += 4;
|
|
}
|
|
}
|
|
|
|
// Now load the image.
|
|
_twirl_bitmaps[step] = CreateDIBitmap(dc, &bmih, CBM_INIT, (const char *)new_data, &bmi, 0);
|
|
}
|
|
|
|
ReleaseDC(_hwnd, dc);
|
|
}
|
|
#endif // _WIN32
|
|
|
|
#ifdef _WIN32
|
|
/**
|
|
* Paints the twirling icon into the browser window before the instance has
|
|
* started.
|
|
*/
|
|
void PPInstance::
|
|
win_paint_twirl(HWND hwnd, HDC dc) {
|
|
RECT rect;
|
|
GetClientRect(_hwnd, &rect);
|
|
int width = rect.right - rect.left;
|
|
int height = rect.bottom - rect.top;
|
|
|
|
// Double-buffer with an offscreen bitmap first.
|
|
HDC bdc = CreateCompatibleDC(dc);
|
|
HBITMAP buffer = CreateCompatibleBitmap(dc, width, height);
|
|
SelectObject(bdc, buffer);
|
|
|
|
// Start by painting the background color.
|
|
FillRect(bdc, &rect, _bg_brush);
|
|
|
|
if (!_started) {
|
|
DWORD now = GetTickCount();
|
|
// Don't draw the twirling icon until at least half a second has passed,
|
|
// so we don't distract people by drawing it unnecessarily.
|
|
if (_failed || (now - _init_time) >= 500) {
|
|
// Which frame are we drawing?
|
|
int step = (now / 100) % twirl_num_steps;
|
|
if (_failed) {
|
|
step = twirl_num_steps;
|
|
}
|
|
|
|
HBITMAP twirl = _twirl_bitmaps[step];
|
|
|
|
int left = rect.left + (width - twirl_width) / 2;
|
|
int top = rect.top + (height - twirl_height) / 2;
|
|
|
|
HDC mem_dc = CreateCompatibleDC(bdc);
|
|
SelectObject(mem_dc, twirl);
|
|
|
|
BitBlt(bdc, left, top, twirl_width, twirl_height,
|
|
mem_dc, 0, 0, SRCCOPY);
|
|
|
|
SelectObject(mem_dc, NULL);
|
|
DeleteDC(mem_dc);
|
|
}
|
|
}
|
|
|
|
// Now blit the buffer to the window.
|
|
BitBlt(dc, 0, 0, width, height, bdc, 0, 0, SRCCOPY);
|
|
}
|
|
#endif // _WIN32
|
|
|
|
#ifdef MACOSX_HAS_EVENT_MODELS
|
|
/**
|
|
* Copies the NPCocoaEvent structure componentwise into a P3DCocoaEvent
|
|
* structure, for passing into the core API.
|
|
*
|
|
* The aux_data object is used to manage temporary storage on the strings
|
|
* created for the event.
|
|
*/
|
|
void PPInstance::
|
|
copy_cocoa_event(P3DCocoaEvent *p3d_event, NPCocoaEvent *np_event,
|
|
EventAuxData &aux_data) {
|
|
p3d_event->version = np_event->version;
|
|
|
|
switch (np_event->type) {
|
|
case NPCocoaEventDrawRect:
|
|
p3d_event->type = P3DCocoaEventDrawRect;
|
|
break;
|
|
case NPCocoaEventMouseDown:
|
|
p3d_event->type = P3DCocoaEventMouseDown;
|
|
break;
|
|
case NPCocoaEventMouseUp:
|
|
p3d_event->type = P3DCocoaEventMouseUp;
|
|
break;
|
|
case NPCocoaEventMouseMoved:
|
|
p3d_event->type = P3DCocoaEventMouseMoved;
|
|
break;
|
|
case NPCocoaEventMouseEntered:
|
|
p3d_event->type = P3DCocoaEventMouseEntered;
|
|
break;
|
|
case NPCocoaEventMouseExited:
|
|
p3d_event->type = P3DCocoaEventMouseExited;
|
|
break;
|
|
case NPCocoaEventMouseDragged:
|
|
p3d_event->type = P3DCocoaEventMouseDragged;
|
|
break;
|
|
case NPCocoaEventKeyDown:
|
|
p3d_event->type = P3DCocoaEventKeyDown;
|
|
break;
|
|
case NPCocoaEventKeyUp:
|
|
p3d_event->type = P3DCocoaEventKeyUp;
|
|
break;
|
|
case NPCocoaEventFlagsChanged:
|
|
p3d_event->type = P3DCocoaEventFlagsChanged;
|
|
break;
|
|
case NPCocoaEventFocusChanged:
|
|
p3d_event->type = P3DCocoaEventFocusChanged;
|
|
break;
|
|
case NPCocoaEventWindowFocusChanged:
|
|
p3d_event->type = P3DCocoaEventWindowFocusChanged;
|
|
break;
|
|
case NPCocoaEventScrollWheel:
|
|
p3d_event->type = P3DCocoaEventScrollWheel;
|
|
break;
|
|
case NPCocoaEventTextInput:
|
|
p3d_event->type = P3DCocoaEventTextInput;
|
|
break;
|
|
}
|
|
|
|
switch (np_event->type) {
|
|
case NPCocoaEventDrawRect:
|
|
p3d_event->data.draw.context = np_event->data.draw.context;
|
|
p3d_event->data.draw.x = np_event->data.draw.x;
|
|
p3d_event->data.draw.y = np_event->data.draw.y;
|
|
p3d_event->data.draw.width = np_event->data.draw.width;
|
|
p3d_event->data.draw.height = np_event->data.draw.height;
|
|
break;
|
|
|
|
case NPCocoaEventMouseDown:
|
|
case NPCocoaEventMouseUp:
|
|
case NPCocoaEventMouseMoved:
|
|
case NPCocoaEventMouseEntered:
|
|
case NPCocoaEventMouseExited:
|
|
case NPCocoaEventMouseDragged:
|
|
case NPCocoaEventScrollWheel:
|
|
p3d_event->data.mouse.modifierFlags = np_event->data.mouse.modifierFlags;
|
|
p3d_event->data.mouse.pluginX = np_event->data.mouse.pluginX;
|
|
p3d_event->data.mouse.pluginY = np_event->data.mouse.pluginY;
|
|
p3d_event->data.mouse.buttonNumber = np_event->data.mouse.buttonNumber;
|
|
p3d_event->data.mouse.clickCount = np_event->data.mouse.clickCount;
|
|
p3d_event->data.mouse.deltaX = np_event->data.mouse.deltaX;
|
|
p3d_event->data.mouse.deltaY = np_event->data.mouse.deltaY;
|
|
p3d_event->data.mouse.deltaZ = np_event->data.mouse.deltaZ;
|
|
break;
|
|
|
|
case NPCocoaEventKeyDown:
|
|
case NPCocoaEventKeyUp:
|
|
case NPCocoaEventFlagsChanged:
|
|
p3d_event->data.key.modifierFlags = np_event->data.key.modifierFlags;
|
|
p3d_event->data.key.characters =
|
|
make_ansi_string(aux_data._characters, np_event->data.key.characters);
|
|
p3d_event->data.key.charactersIgnoringModifiers =
|
|
make_ansi_string(aux_data._characters_im, np_event->data.key.charactersIgnoringModifiers);
|
|
p3d_event->data.key.isARepeat = np_event->data.key.isARepeat;
|
|
p3d_event->data.key.keyCode = np_event->data.key.keyCode;
|
|
break;
|
|
|
|
case NPCocoaEventFocusChanged:
|
|
case NPCocoaEventWindowFocusChanged:
|
|
p3d_event->data.focus.hasFocus = np_event->data.focus.hasFocus;
|
|
break;
|
|
|
|
case NPCocoaEventTextInput:
|
|
p3d_event->data.text.text =
|
|
make_ansi_string(aux_data._text, np_event->data.text.text);
|
|
break;
|
|
}
|
|
}
|
|
#endif // MACOSX_HAS_EVENT_MODELS
|
|
|
|
#ifdef MACOSX_HAS_EVENT_MODELS
|
|
/**
|
|
* OSX only: Fills result with the unicode characters in the NPNSString. Also
|
|
* returns result.c_str().
|
|
*/
|
|
const wchar_t *PPInstance::
|
|
make_ansi_string(wstring &result, NPNSString *ns_string) {
|
|
result.clear();
|
|
|
|
if (ns_string != NULL) {
|
|
// An NPNSString is really just an NSString, which is itself just a
|
|
// CFString.
|
|
CFStringRef cfstr = (CFStringRef)ns_string;
|
|
CFIndex length = CFStringGetLength(cfstr);
|
|
|
|
for (CFIndex i = 0; i < length; ++i) {
|
|
result += (wchar_t)CFStringGetCharacterAtIndex(cfstr, i);
|
|
}
|
|
}
|
|
|
|
return result.c_str();
|
|
}
|
|
#endif // MACOSX_HAS_EVENT_MODELS
|
|
|
|
#ifdef MACOSX_HAS_EVENT_MODELS
|
|
/**
|
|
* Locally processes a Cocoa event for the window before sending it down to
|
|
* the Core API. This is used for drawing a twirling icon in the window while
|
|
* the Core API is downloading.
|
|
*/
|
|
void PPInstance::
|
|
handle_cocoa_event(const P3DCocoaEvent *p3d_event) {
|
|
switch (p3d_event->type) {
|
|
case P3DCocoaEventDrawRect:
|
|
if (!_started) {
|
|
CGContextRef context = p3d_event->data.draw.context;
|
|
paint_twirl_osx_cgcontext(context);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
#endif // MACOSX_HAS_EVENT_MODELS
|
|
|
|
#ifdef MACOSX_HAS_EVENT_MODELS
|
|
/**
|
|
* Fills _twirl_images with an array of images for drawing the twirling icon
|
|
* while we're waiting for the instance to load.
|
|
*/
|
|
void PPInstance::
|
|
osx_get_twirl_images() {
|
|
if (_got_twirl_images) {
|
|
return;
|
|
}
|
|
_got_twirl_images = true;
|
|
|
|
static const size_t twirl_size = twirl_width * twirl_height;
|
|
unsigned char twirl_data[twirl_size * 3];
|
|
|
|
for (int step = 0; step < twirl_num_steps + 1; ++step) {
|
|
get_twirl_data(twirl_data, twirl_size, step,
|
|
_fgcolor_r, _fgcolor_g, _fgcolor_b,
|
|
_bgcolor_r, _bgcolor_g, _bgcolor_b);
|
|
|
|
unsigned char *new_data = new unsigned char[twirl_size * 4];
|
|
|
|
// Expand the RGB channels into RGBA. Flip upside-down too.
|
|
for (int yi = 0; yi < twirl_height; ++yi) {
|
|
const unsigned char *sp = twirl_data + (twirl_height - 1 - yi) * twirl_width * 3;
|
|
unsigned char *dp = new_data + yi * twirl_width * 4;
|
|
for (int xi = 0; xi < twirl_width; ++xi) {
|
|
// RGB <= BGR.
|
|
dp[0] = sp[2];
|
|
dp[1] = sp[1];
|
|
dp[2] = sp[0];
|
|
dp[3] = (unsigned char)0xff;
|
|
sp += 3;
|
|
dp += 4;
|
|
}
|
|
}
|
|
|
|
OsxImageData &image = _twirl_images[step];
|
|
image._raw_data = new_data;
|
|
|
|
image._data =
|
|
CFDataCreateWithBytesNoCopy(NULL, (const UInt8 *)image._raw_data,
|
|
twirl_size * 4, kCFAllocatorNull);
|
|
image._provider = CGDataProviderCreateWithCFData(image._data);
|
|
image._color_space = CGColorSpaceCreateDeviceRGB();
|
|
|
|
image._image =
|
|
CGImageCreate(twirl_width, twirl_height, 8, 32,
|
|
twirl_width * 4, image._color_space,
|
|
kCGImageAlphaFirst | kCGBitmapByteOrder32Little,
|
|
image._provider, NULL, false, kCGRenderingIntentDefault);
|
|
}
|
|
}
|
|
#endif // MACOSX_HAS_EVENT_MODELS
|
|
|
|
#ifdef MACOSX_HAS_EVENT_MODELS
|
|
/**
|
|
* Frees the twirl_images array.
|
|
*/
|
|
void PPInstance::
|
|
osx_release_twirl_images() {
|
|
if (!_got_twirl_images) {
|
|
return;
|
|
}
|
|
_got_twirl_images = false;
|
|
|
|
for (int step = 0; step < twirl_num_steps + 1; ++step) {
|
|
OsxImageData &image = _twirl_images[step];
|
|
|
|
if (image._image != NULL) {
|
|
CGImageRelease(image._image);
|
|
image._image = NULL;
|
|
}
|
|
if (image._color_space != NULL) {
|
|
CGColorSpaceRelease(image._color_space);
|
|
image._color_space = NULL;
|
|
}
|
|
if (image._provider != NULL) {
|
|
CGDataProviderRelease(image._provider);
|
|
image._provider = NULL;
|
|
}
|
|
if (image._data != NULL) {
|
|
CFRelease(image._data);
|
|
image._data = NULL;
|
|
}
|
|
if (image._raw_data != NULL) {
|
|
delete[] image._raw_data;
|
|
image._raw_data = NULL;
|
|
}
|
|
}
|
|
}
|
|
#endif // MACOSX_HAS_EVENT_MODELS
|
|
|
|
#ifdef MACOSX_HAS_EVENT_MODELS
|
|
/**
|
|
* Actually paints the twirling icon in the OSX window, using Core Graphics.
|
|
* (We don't bother painting it in the older Cocoa interface.)
|
|
*/
|
|
void PPInstance::
|
|
paint_twirl_osx_cgcontext(CGContextRef context) {
|
|
// Clear the whole region to the bgcolor before beginning.
|
|
float bg_components[] = { _bgcolor_r / 255.0f, _bgcolor_g / 255.0f, _bgcolor_b / 255.0f, 1 };
|
|
CGColorSpaceRef rgb_space = CGColorSpaceCreateDeviceRGB();
|
|
CGColorRef bg = CGColorCreate(rgb_space, bg_components);
|
|
|
|
CGRect region = { { 0, 0 }, { _window.width, _window.height } };
|
|
CGContextBeginPath(context);
|
|
CGContextSetFillColorWithColor(context, bg);
|
|
CGContextAddRect(context, region);
|
|
CGContextFillPath(context);
|
|
|
|
CGColorRelease(bg);
|
|
CGColorSpaceRelease(rgb_space);
|
|
|
|
if (_failed) {
|
|
// Draw the failed icon if something went wrong.
|
|
osx_paint_image(context, _twirl_images[twirl_num_steps]);
|
|
|
|
} else {
|
|
struct timeval tv;
|
|
gettimeofday(&tv, (struct timezone *)NULL);
|
|
double now = (double)(tv.tv_sec - _init_sec) + (double)(tv.tv_usec - _init_usec) / 1000000.0;
|
|
|
|
// Don't draw the twirling icon until at least half a second has passed,
|
|
// so we don't distract people by drawing it unnecessarily.
|
|
if (now >= 0.5) {
|
|
int step = ((int)(now * 10.0)) % twirl_num_steps;
|
|
osx_paint_image(context, _twirl_images[step]);
|
|
}
|
|
}
|
|
}
|
|
#endif // MACOSX_HAS_EVENT_MODELS
|
|
|
|
#ifdef MACOSX_HAS_EVENT_MODELS
|
|
/**
|
|
* Draws the indicated image, centered within the window. Returns true on
|
|
* success, false if the image is not defined.
|
|
*/
|
|
bool PPInstance::
|
|
osx_paint_image(CGContextRef context, const OsxImageData &image) {
|
|
if (image._image == NULL) {
|
|
return false;
|
|
}
|
|
|
|
// Determine the relative size of image and window.
|
|
int win_cx = _window.width / 2;
|
|
int win_cy = _window.height / 2;
|
|
|
|
CGRect rect = { { 0, 0 }, { 0, 0 } };
|
|
|
|
// The bitmap fits within the window; center it.
|
|
|
|
// This is the top-left corner of the bitmap in window coordinates.
|
|
int p_x = win_cx - twirl_width / 2;
|
|
int p_y = win_cy - twirl_height / 2;
|
|
|
|
rect.origin.x += p_x;
|
|
rect.origin.y += p_y;
|
|
rect.size.width = twirl_width;
|
|
rect.size.height = twirl_height;
|
|
|
|
CGContextDrawImage(context, rect, image._image);
|
|
|
|
return true;
|
|
}
|
|
#endif // MACOSX_HAS_EVENT_MODELS
|
|
|
|
#ifdef __APPLE__
|
|
/**
|
|
* OSX only: this callback is associated with a CFRunLoopTimer; it's used to
|
|
* forward request messages to the main thread.
|
|
*/
|
|
void PPInstance::
|
|
timer_callback(CFRunLoopTimerRef timer, void *info) {
|
|
PPInstance *self = (PPInstance *)info;
|
|
ACQUIRE_LOCK(self->_timer_lock);
|
|
if (self->_request_timer != NULL) {
|
|
CFRunLoopTimerInvalidate(self->_request_timer);
|
|
CFRelease(self->_request_timer);
|
|
self->_request_timer = NULL;
|
|
}
|
|
RELEASE_LOCK(self->_timer_lock);
|
|
|
|
self->handle_request_loop();
|
|
}
|
|
#endif // __APPLE__
|
|
|
|
#ifdef __APPLE__
|
|
/**
|
|
* OSX only: this callback is used to twirl the icon before the instance
|
|
* loads.
|
|
*/
|
|
void PPInstance::
|
|
st_twirl_timer_callback(CFRunLoopTimerRef timer, void *info) {
|
|
PPInstance *self = (PPInstance *)info;
|
|
self->twirl_timer_callback();
|
|
}
|
|
#endif // __APPLE__
|
|
|
|
#ifdef __APPLE__
|
|
/**
|
|
* OSX only: this callback is used to twirl the icon before the instance
|
|
* loads.
|
|
*/
|
|
void PPInstance::
|
|
twirl_timer_callback() {
|
|
if (_got_window) {
|
|
NPRect rect = { 0, 0, (unsigned short)_window.height, (unsigned short)_window.width };
|
|
browser->invalidaterect(_npp_instance, &rect);
|
|
}
|
|
}
|
|
#endif // __APPLE__
|
|
|
|
#ifdef HAVE_X11
|
|
/**
|
|
* Spawns a separate process to twirl the loading icon in the X11 browser
|
|
* window.
|
|
*/
|
|
void PPInstance::
|
|
x11_start_twirl_subprocess() {
|
|
if (_twirl_subprocess_pid != -1) {
|
|
// Already started.
|
|
return;
|
|
}
|
|
|
|
// Fork and exec.
|
|
pid_t child = fork();
|
|
if (child < 0) {
|
|
perror("fork");
|
|
return;
|
|
}
|
|
|
|
if (child == 0) {
|
|
// Here we are in the child process.
|
|
x11_twirl_subprocess_run();
|
|
_exit(99);
|
|
}
|
|
|
|
// In the parent process.
|
|
_twirl_subprocess_pid = child;
|
|
nout << "Started twirl subprocess, pid " << _twirl_subprocess_pid
|
|
<< "\n";
|
|
}
|
|
#endif // HAVE_X11
|
|
|
|
#ifdef HAVE_X11
|
|
/**
|
|
* Kills the twirl process that was started earlier.
|
|
*/
|
|
void PPInstance::
|
|
x11_stop_twirl_subprocess() {
|
|
if (_twirl_subprocess_pid == -1) {
|
|
// Already stopped.
|
|
return;
|
|
}
|
|
|
|
kill(_twirl_subprocess_pid, SIGKILL);
|
|
|
|
int status;
|
|
pid_t result = waitpid(_twirl_subprocess_pid, &status, 0);
|
|
|
|
nout << "Twirl window process has successfully stopped.\n";
|
|
if (WIFEXITED(status)) {
|
|
nout << " exited normally, status = "
|
|
<< WEXITSTATUS(status) << "\n";
|
|
} else if (WIFSIGNALED(status)) {
|
|
nout << " signalled by " << WTERMSIG(status) << ", core = "
|
|
<< WCOREDUMP(status) << "\n";
|
|
} else if (WIFSTOPPED(status)) {
|
|
nout << " stopped by " << WSTOPSIG(status) << "\n";
|
|
}
|
|
_twirl_subprocess_pid = -1;
|
|
}
|
|
#endif // HAVE_X11
|
|
|
|
#ifdef HAVE_X11
|
|
/**
|
|
* The code that is run within a subprocess. This code is responsible for
|
|
* twirling the loading icon endlessly.
|
|
*/
|
|
void PPInstance::
|
|
x11_twirl_subprocess_run() {
|
|
// Since everything within this function happens within a subprocess that
|
|
// will just exit, we can be a little sloppy with our resource allocation.
|
|
// It is all done directly within this function, and we don't need to worry
|
|
// about freeing stuff.
|
|
|
|
// First, sleep for 0.5 seconds, so we don't start twirling right away (to
|
|
// avoid distracting the user unnecessarily).
|
|
|
|
struct timespec req;
|
|
req.tv_sec = 0;
|
|
req.tv_nsec = 500000000; // 500 ms
|
|
nanosleep(&req, NULL);
|
|
|
|
// We haven't been killed yet, so the plugin is still loading. Start
|
|
// twirling.
|
|
|
|
// First, embed a window.
|
|
X11_Display *display = XOpenDisplay(NULL);
|
|
assert(display != NULL);
|
|
int screen = DefaultScreen(display);
|
|
|
|
int depth = DefaultDepth(display, screen);
|
|
Visual *dvisual = DefaultVisual(display, screen);
|
|
|
|
long event_mask = ExposureMask;
|
|
|
|
// Allocate the foreground and background colors.
|
|
Colormap colormap = DefaultColormap(display, screen);
|
|
|
|
XColor fg;
|
|
fg.red = _fgcolor_r * 0x101;
|
|
fg.green = _fgcolor_g * 0x101;
|
|
fg.blue = _fgcolor_b * 0x101;
|
|
fg.flags = DoRed | DoGreen | DoBlue;
|
|
unsigned long fg_pixel = -1;
|
|
if (XAllocColor(display, colormap, &fg)) {
|
|
fg_pixel = fg.pixel;
|
|
}
|
|
|
|
XColor bg;
|
|
bg.red = _bgcolor_r * 0x101;
|
|
bg.green = _bgcolor_g * 0x101;
|
|
bg.blue = _bgcolor_b * 0x101;
|
|
bg.flags = DoRed | DoGreen | DoBlue;
|
|
unsigned long bg_pixel = -1;
|
|
if (XAllocColor(display, colormap, &bg)) {
|
|
bg_pixel = bg.pixel;
|
|
}
|
|
|
|
// Initialize window attributes
|
|
XSetWindowAttributes wa;
|
|
wa.background_pixel = XWhitePixel(display, screen);
|
|
if (bg_pixel != -1) {
|
|
wa.background_pixel = bg_pixel;
|
|
}
|
|
wa.border_pixel = 0;
|
|
wa.event_mask = event_mask;
|
|
|
|
unsigned long attrib_mask = CWBackPixel | CWBorderPixel | CWEventMask;
|
|
|
|
X11_Window parent = 0;
|
|
if (_use_xembed) {
|
|
#ifdef HAVE_GTK
|
|
assert(_plug != NULL);
|
|
parent = GDK_DRAWABLE_XID(_plug->window);
|
|
#endif // HAVE_GTK
|
|
} else {
|
|
parent = (X11_Window)(_window.window);
|
|
}
|
|
|
|
X11_Window window = XCreateWindow
|
|
(display, parent, 0, 0, _window.width, _window.height,
|
|
0, depth, InputOutput, dvisual, attrib_mask, &wa);
|
|
XMapWindow(display, window);
|
|
|
|
// Create a graphics context.
|
|
XGCValues gcval;
|
|
gcval.function = GXcopy;
|
|
gcval.plane_mask = AllPlanes;
|
|
gcval.foreground = BlackPixel(display, screen);
|
|
if (fg_pixel != -1) {
|
|
gcval.foreground = fg_pixel;
|
|
}
|
|
gcval.background = WhitePixel(display, screen);
|
|
if (bg_pixel != -1) {
|
|
gcval.background = bg_pixel;
|
|
}
|
|
GC graphics_context = XCreateGC(display, window,
|
|
GCFunction | GCPlaneMask | GCForeground | GCBackground, &gcval);
|
|
|
|
// Load up the twirling images.
|
|
XImage *images[twirl_num_steps];
|
|
double r_ratio = dvisual->red_mask / 255.0;
|
|
double g_ratio = dvisual->green_mask / 255.0;
|
|
double b_ratio = dvisual->blue_mask / 255.0;
|
|
|
|
static const size_t twirl_size = twirl_width * twirl_height;
|
|
unsigned char twirl_data[twirl_size * 3];
|
|
|
|
for (int step = 0; step < twirl_num_steps; ++step) {
|
|
get_twirl_data(twirl_data, twirl_size, step,
|
|
_fgcolor_r, _fgcolor_g, _fgcolor_b,
|
|
_bgcolor_r, _bgcolor_g, _bgcolor_b);
|
|
uint32_t *new_data = new uint32_t[twirl_size];
|
|
int j = 0;
|
|
for (int i = 0; i < twirl_size * 3; i += 3) {
|
|
unsigned int r, g, b;
|
|
r = (unsigned int)(twirl_data[i+0] * r_ratio);
|
|
g = (unsigned int)(twirl_data[i+1] * g_ratio);
|
|
b = (unsigned int)(twirl_data[i+2] * b_ratio);
|
|
new_data[j++] = ((r & dvisual->red_mask) |
|
|
(g & dvisual->green_mask) |
|
|
(b & dvisual->blue_mask));
|
|
}
|
|
|
|
// Now load the image.
|
|
images[step] = XCreateImage(display, CopyFromParent, DefaultDepth(display, screen),
|
|
ZPixmap, 0, (char *)new_data, twirl_width, twirl_height, 32, 0);
|
|
}
|
|
|
|
// Now twirl indefinitely, until our parent process kills us.
|
|
bool needs_redraw = true;
|
|
int last_step = -1;
|
|
while (true) {
|
|
// First, scan for X events.
|
|
XEvent event;
|
|
while (XCheckWindowEvent(display, window, ~0, &event)) {
|
|
switch (event.type) {
|
|
case Expose:
|
|
case GraphicsExpose:
|
|
needs_redraw = true;
|
|
break;
|
|
}
|
|
|
|
// We should probably track the resize event, but this window will be
|
|
// short-lived (and probably won't have an opportunity to resize anyway)
|
|
// so we don't bother.
|
|
}
|
|
|
|
// What step are we on now?
|
|
struct timeval tv;
|
|
gettimeofday(&tv, (struct timezone *)NULL);
|
|
double now = (double)(tv.tv_sec - _init_sec) + (double)(tv.tv_usec - _init_usec) / 1000000.0;
|
|
int step = ((int)(now * 10.0)) % twirl_num_steps;
|
|
if (step != last_step) {
|
|
needs_redraw = true;
|
|
}
|
|
|
|
if (needs_redraw) {
|
|
XClearWindow(display, window);
|
|
int xo = (_window.width - twirl_width) / 2;
|
|
int yo = (_window.height - twirl_height) / 2;
|
|
XPutImage(display, window, graphics_context, images[step], 0, 0,
|
|
xo, yo, twirl_width, twirl_height);
|
|
|
|
XFlush(display);
|
|
needs_redraw = false;
|
|
last_step = step;
|
|
}
|
|
|
|
struct timespec req;
|
|
req.tv_sec = 0;
|
|
req.tv_nsec = 100000000; // 100 ms
|
|
nanosleep(&req, NULL);
|
|
}
|
|
}
|
|
#endif // HAVE_X11
|
|
|
|
/**
|
|
*
|
|
*/
|
|
PPInstance::StreamingFileData::
|
|
StreamingFileData(PPDownloadRequest *req, const string &filename,
|
|
P3D_instance *p3d_inst) :
|
|
_p3d_inst(p3d_inst),
|
|
_user_id(req->_user_id),
|
|
_filename(filename),
|
|
_file(filename.c_str(), ios::in | ios::binary)
|
|
{
|
|
// First, seek to the end to get the file size.
|
|
_file.seekg(0, ios::end);
|
|
_file_size = _file.tellg();
|
|
_total_count = 0;
|
|
|
|
// Then return to the beginning to read the data.
|
|
_file.seekg(0, ios::beg);
|
|
|
|
// Now start up the thread.
|
|
_thread_done = false;
|
|
_thread_continue = true;
|
|
INIT_THREAD(_thread);
|
|
|
|
SPAWN_THREAD(_thread, thread_run, this);
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
PPInstance::StreamingFileData::
|
|
~StreamingFileData() {
|
|
// Time to stop.
|
|
_thread_continue = false;
|
|
|
|
JOIN_THREAD(_thread);
|
|
}
|
|
|
|
/**
|
|
* Returns true if the file has been fully read and this object is ready to be
|
|
* deleted, or false if there is more work to do.
|
|
*/
|
|
bool PPInstance::StreamingFileData::
|
|
is_done() const {
|
|
return _thread_done;
|
|
}
|
|
|
|
/**
|
|
* The main function of the file thread. This reads the file contents and
|
|
* feeds it to the core API.
|
|
*/
|
|
void PPInstance::StreamingFileData::
|
|
thread_run() {
|
|
static const size_t buffer_size = 81920;
|
|
// static const size_t buffer_size = 512;
|
|
char buffer[buffer_size];
|
|
|
|
_file.read(buffer, buffer_size);
|
|
size_t count = _file.gcount();
|
|
while (count != 0) {
|
|
_total_count += count;
|
|
bool download_ok = P3D_instance_feed_url_stream_ptr
|
|
(_p3d_inst, _user_id, P3D_RC_in_progress,
|
|
0, _file_size, (const unsigned char *)buffer, count);
|
|
|
|
if (!download_ok) {
|
|
// Never mind.
|
|
_thread_done = true;
|
|
return;
|
|
}
|
|
|
|
if (!_thread_continue) {
|
|
// Interrupted by the main thread. Presumably we're being shut down.
|
|
_thread_done = true;
|
|
return;
|
|
}
|
|
|
|
// So far, so good. Read some more.
|
|
_file.read(buffer, buffer_size);
|
|
count = _file.gcount();
|
|
|
|
// This is useful for development, to slow things down enough to see the
|
|
// progress bar move.
|
|
#ifdef _WIN32
|
|
Sleep(10);
|
|
#else
|
|
struct timeval tv;
|
|
tv.tv_sec = 0;
|
|
tv.tv_usec = 10000;
|
|
select(0, NULL, NULL, NULL, &tv);
|
|
#endif
|
|
}
|
|
|
|
// End of file.
|
|
P3D_result_code result = P3D_RC_done;
|
|
if (_file.fail() && !_file.eof()) {
|
|
// Got an error while reading.
|
|
result = P3D_RC_generic_error;
|
|
}
|
|
|
|
P3D_instance_feed_url_stream_ptr
|
|
(_p3d_inst, _user_id, result, 0, _total_count, NULL, 0);
|
|
|
|
// All done.
|
|
_thread_done = true;
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
PPInstance::StreamTempFile::
|
|
StreamTempFile() {
|
|
_opened = false;
|
|
_finished = false;
|
|
_current_size = 0;
|
|
_total_size = 0;
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
PPInstance::StreamTempFile::
|
|
~StreamTempFile() {
|
|
cleanup();
|
|
}
|
|
|
|
/**
|
|
* Creates the temp file and prepares to write to it. It is not normally
|
|
* necessary to call this explicitly; it will be called automatically on the
|
|
* first call to feed().
|
|
*/
|
|
void PPInstance::StreamTempFile::
|
|
open() {
|
|
assert(!_opened);
|
|
_opened = true;
|
|
_finished = false;
|
|
_current_size = 0;
|
|
_total_size = 0;
|
|
|
|
char *name = tempnam(NULL, "p3d_");
|
|
_filename = name;
|
|
free(name);
|
|
|
|
_stream.clear();
|
|
_stream.open(_filename.c_str(), ios::binary);
|
|
if (!_stream) {
|
|
nout << "Unable to open temp file " << _filename << "\n";
|
|
} else {
|
|
nout << "Opening " << _filename << "\n";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Receives new data from the URL and writes it to the temp file. Returns
|
|
* true on success, false on failure.
|
|
*/
|
|
bool PPInstance::StreamTempFile::
|
|
feed(size_t total_expected_data, const void *this_data,
|
|
size_t this_data_size) {
|
|
if (_finished) {
|
|
nout << "feed(" << total_expected_data << ", " << (void *)this_data
|
|
<< ", " << this_data_size << ") to " << _filename
|
|
<< ", but already finished at " << _current_size << "\n";
|
|
return false;
|
|
}
|
|
|
|
if (!_opened) {
|
|
open();
|
|
}
|
|
|
|
_stream.write((const char *)this_data, this_data_size);
|
|
_current_size += this_data_size;
|
|
_total_size = total_expected_data;
|
|
|
|
if (!_stream) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Marks the end of the data received from the URL. The file is closed but
|
|
* not yet deleted; it remains on disk and may be read at leisure.
|
|
*/
|
|
void PPInstance::StreamTempFile::
|
|
finish() {
|
|
if (!_finished) {
|
|
_finished = true;
|
|
_total_size = _current_size;
|
|
}
|
|
|
|
_stream.close();
|
|
}
|
|
|
|
/**
|
|
* Closes the stream for more data. The file is not yet deleted; it remains
|
|
* on disk and may be read at leisure.
|
|
*/
|
|
void PPInstance::StreamTempFile::
|
|
close() {
|
|
_stream.close();
|
|
}
|
|
|
|
/**
|
|
* Closes all open processes and removes the temp file from disk.
|
|
*/
|
|
void PPInstance::StreamTempFile::
|
|
cleanup() {
|
|
finish();
|
|
|
|
if (!_filename.empty()) {
|
|
nout << "Deleting " << _filename << "\n";
|
|
unlink(_filename.c_str());
|
|
_filename.clear();
|
|
}
|
|
|
|
_opened = false;
|
|
_finished = false;
|
|
}
|