3937 lines
121 KiB
C++
3937 lines
121 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 p3dInstance.cxx
|
|
* @author drose
|
|
* @date 2009-05-29
|
|
*/
|
|
|
|
#include "p3dInstance.h"
|
|
#include "p3dInstanceManager.h"
|
|
#include "p3dDownload.h"
|
|
#include "p3dSession.h"
|
|
#include "p3dPackage.h"
|
|
#include "p3dSplashWindow.h"
|
|
#include "p3dWinSplashWindow.h"
|
|
#include "p3dOsxSplashWindow.h"
|
|
#include "p3dX11SplashWindow.h"
|
|
#include "p3dObject.h"
|
|
#include "p3dMainObject.h"
|
|
#include "p3dUndefinedObject.h"
|
|
#include "p3dMultifileReader.h"
|
|
#include "p3dTemporaryFile.h"
|
|
#include "parse_color.h"
|
|
|
|
#include <sstream>
|
|
#include <algorithm>
|
|
|
|
#ifdef __APPLE__
|
|
#include <sys/mman.h>
|
|
#include <ApplicationServices/ApplicationServices.h>
|
|
|
|
using std::max;
|
|
using std::min;
|
|
using std::ostream;
|
|
using std::ostringstream;
|
|
using std::stringstream;
|
|
using std::string;
|
|
using std::vector;
|
|
|
|
// Lifted from NSEvent.h (which is Objective-C).
|
|
enum {
|
|
NSAlphaShiftKeyMask = 1 << 16,
|
|
NSShiftKeyMask = 1 << 17,
|
|
NSControlKeyMask = 1 << 18,
|
|
NSAlternateKeyMask = 1 << 19,
|
|
NSCommandKeyMask = 1 << 20,
|
|
NSNumericPadKeyMask = 1 << 21,
|
|
NSHelpKeyMask = 1 << 22,
|
|
NSFunctionKeyMask = 1 << 23,
|
|
NSDeviceIndependentModifierFlagsMask = 0xffff0000U
|
|
};
|
|
|
|
#endif // __APPLE__
|
|
|
|
#ifdef _WIN32
|
|
typedef P3DWinSplashWindow SplashWindowType;
|
|
#elif defined(__APPLE__) && !__LP64__
|
|
typedef P3DOsxSplashWindow SplashWindowType;
|
|
#elif defined(HAVE_X11)
|
|
typedef P3DX11SplashWindow SplashWindowType;
|
|
#else
|
|
typedef P3DSplashWindow SplashWindowType;
|
|
#endif
|
|
|
|
// The amount of time (in seconds) over which we average the total download
|
|
// time, for smoothing out the time estimate.
|
|
static const double time_average = 10.0;
|
|
|
|
// These are the various image files we might download for use in the splash
|
|
// window. This list must match the ImageType enum.
|
|
const char *P3DInstance::_image_type_names[P3DInstance::IT_num_image_types] = {
|
|
"download",
|
|
"unauth",
|
|
"ready",
|
|
"failed",
|
|
"launch",
|
|
"active",
|
|
"auth_ready",
|
|
"auth_rollover",
|
|
"auth_click",
|
|
"play_ready",
|
|
"play_rollover",
|
|
"play_click",
|
|
"none", // Not really used.
|
|
};
|
|
|
|
static void
|
|
write_str(ostream &out, const wchar_t *str) {
|
|
const wchar_t *p = str;
|
|
while (*p != 0) {
|
|
out << (int)*p << ' ';
|
|
++p;
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
P3DInstance::
|
|
P3DInstance(P3D_request_ready_func *func,
|
|
const P3D_token tokens[], size_t num_tokens,
|
|
int argc, const char *argv[], void *user_data) :
|
|
_func(func)
|
|
{
|
|
_dom_object = nullptr;
|
|
_main_object = new P3DMainObject;
|
|
_main_object->set_instance(this);
|
|
_user_data = user_data;
|
|
_request_pending = false;
|
|
_total_time_reports = 0;
|
|
_temp_p3d_filename = nullptr;
|
|
_image_package = nullptr;
|
|
_current_background_image = IT_none;
|
|
_current_button_image = IT_none;
|
|
_got_fparams = false;
|
|
_got_wparams = false;
|
|
_p3d_trusted = false;
|
|
_xpackage = nullptr;
|
|
_certlist_package = nullptr;
|
|
_p3dcert_package = nullptr;
|
|
|
|
_fparams.set_tokens(tokens, num_tokens);
|
|
_fparams.set_args(argc, argv);
|
|
|
|
nout << "Creating P3DInstance " << this << ": ";
|
|
for (int i = 0; i < _fparams.get_num_tokens(); ++i) {
|
|
nout << " " << _fparams.get_token_keyword(i)
|
|
<< "=\"" << _fparams.get_token_value(i) << "\"";
|
|
}
|
|
nout << "\n";
|
|
|
|
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
|
_instance_id = inst_mgr->get_unique_id();
|
|
_hidden = (_fparams.lookup_token_int("hidden") != 0);
|
|
_matches_run_origin = true;
|
|
_matches_script_origin = false;
|
|
_allow_python_dev = false;
|
|
_keep_user_env = (_fparams.lookup_token_int("keep_user_env") != 0);
|
|
_auto_start = (_fparams.lookup_token_int("auto_start") != 0);
|
|
_stop_on_ready = (_fparams.lookup_token_int("stop_on_ready") != 0);
|
|
_auto_install = true;
|
|
if (_fparams.has_token("auto_install")) {
|
|
_auto_install = (_fparams.lookup_token_int("auto_install") != 0);
|
|
}
|
|
_auth_button_clicked = false;
|
|
_failed = false;
|
|
_session = nullptr;
|
|
_auth_session = nullptr;
|
|
_panda3d_package = nullptr;
|
|
_splash_window = nullptr;
|
|
_instance_window_opened = false;
|
|
_instance_window_attached = false;
|
|
_stuff_to_download = false;
|
|
_download_package_index = 0;
|
|
_prev_downloaded = 0;
|
|
_total_download_size = 0;
|
|
_total_downloaded = 0;
|
|
_packages_specified = false;
|
|
_download_started = false;
|
|
_download_complete = false;
|
|
_instance_started = false;
|
|
|
|
INIT_LOCK(_request_lock);
|
|
_requested_stop = false;
|
|
|
|
#ifdef __APPLE__
|
|
_shared_fd = -1;
|
|
_shared_mmap_size = 0;
|
|
_swbuffer = nullptr;
|
|
_reversed_buffer = nullptr;
|
|
_buffer_data = nullptr;
|
|
_data_provider = nullptr;
|
|
_buffer_color_space = nullptr;
|
|
_buffer_image = nullptr;
|
|
|
|
// We have to start with _mouse_active true; firefox doesn't send activate
|
|
// events.
|
|
_mouse_active = true;
|
|
_modifiers = 0;
|
|
_frame_timer = nullptr;
|
|
#endif // __APPLE__
|
|
|
|
// Set some initial properties.
|
|
_main_object->set_float_property("instanceDownloadProgress", 0.0);
|
|
_main_object->set_float_property("downloadProgress", 0.0);
|
|
_main_object->set_undefined_property("downloadElapsedSeconds");
|
|
_main_object->set_undefined_property("downloadElapsedFormatted");
|
|
_main_object->set_undefined_property("downloadRemainingSeconds");
|
|
_main_object->set_undefined_property("downloadRemainingFormatted");
|
|
_main_object->set_string_property("downloadPackageName", "");
|
|
_main_object->set_string_property("downloadPackageDisplayName", "");
|
|
_main_object->set_bool_property("downloadComplete", false);
|
|
_main_object->set_string_property("status", "initial");
|
|
|
|
ostringstream stream;
|
|
stream << inst_mgr->get_plugin_major_version() << "."
|
|
<< inst_mgr->get_plugin_minor_version() << "."
|
|
<< inst_mgr->get_plugin_sequence_version();
|
|
if (!inst_mgr->get_plugin_official_version()) {
|
|
stream << "c";
|
|
}
|
|
|
|
// The plugin version as a single number, with three digits reserved for
|
|
// each component.
|
|
int numeric_version =
|
|
inst_mgr->get_plugin_major_version() * 1000000 +
|
|
inst_mgr->get_plugin_minor_version() * 1000 +
|
|
inst_mgr->get_plugin_sequence_version();
|
|
if (!inst_mgr->get_plugin_official_version()) {
|
|
// Subtract 1 if we are not an official version.
|
|
--numeric_version;
|
|
}
|
|
|
|
_main_object->set_string_property("pluginVersionString", stream.str());
|
|
_main_object->set_int_property("pluginMajorVersion", inst_mgr->get_plugin_major_version());
|
|
_main_object->set_int_property("pluginMinorVersion", inst_mgr->get_plugin_minor_version());
|
|
_main_object->set_int_property("pluginSequenceVersion", inst_mgr->get_plugin_sequence_version());
|
|
_main_object->set_bool_property("pluginOfficialVersion", inst_mgr->get_plugin_official_version());
|
|
_main_object->set_int_property("pluginNumericVersion", numeric_version);
|
|
_main_object->set_string_property("pluginDistributor", inst_mgr->get_plugin_distributor());
|
|
_main_object->set_string_property("coreapiHostUrl", inst_mgr->get_coreapi_host_url());
|
|
time_t timestamp = inst_mgr->get_coreapi_timestamp();
|
|
_main_object->set_int_property("coreapiTimestamp", (int)timestamp);
|
|
const char *timestamp_string = ctime(×tamp);
|
|
if (timestamp_string == nullptr) {
|
|
timestamp_string = "";
|
|
}
|
|
_main_object->set_string_property("coreapiTimestampString", timestamp_string);
|
|
_main_object->set_string_property("coreapiVersionString", inst_mgr->get_coreapi_set_ver());
|
|
|
|
_main_object->set_bool_property("trustedEnvironment", (int)inst_mgr->get_trusted_environment());
|
|
_main_object->set_bool_property("consoleEnvironment", (int)inst_mgr->get_console_environment());
|
|
|
|
// We'll start off with the "download" image displayed in the splash window
|
|
// (when it opens), until we get stuff downloaded.
|
|
set_background_image(IT_download);
|
|
|
|
// We'd better ask for the image package up front, even if it turns out we
|
|
// don't need it for this particular app. We'll probably use it eventually,
|
|
// and it's good to have it loaded early, so we can put up a splash image
|
|
// (for instance, the above IT_download image) while we download the real
|
|
// contents.
|
|
P3DHost *host = inst_mgr->get_host(inst_mgr->get_host_url());
|
|
_image_package = host->get_package("images", "", "", "");
|
|
if (_image_package != nullptr) {
|
|
_image_package->add_instance(this);
|
|
}
|
|
|
|
// Check if the window size has been explicitly set to 0. This means we
|
|
// have an explicitly hidden plugin, and we should be prepared not to get a
|
|
// wparams from the browser.
|
|
if (_fparams.has_token("width") && _fparams.has_token("height") &&
|
|
(_fparams.lookup_token_int("width") == 0 ||
|
|
_fparams.lookup_token_int("height") == 0)) {
|
|
P3D_window_handle dummy_handle;
|
|
memset(&dummy_handle, 0, sizeof(dummy_handle));
|
|
P3DWindowParams wparams(P3D_WT_hidden, 0, 0, 0, 0, dummy_handle);
|
|
set_wparams(wparams);
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
P3DInstance::
|
|
~P3DInstance() {
|
|
assert(_session == nullptr);
|
|
cleanup();
|
|
|
|
if (_dom_object != nullptr) {
|
|
P3D_OBJECT_DECREF(_dom_object);
|
|
_dom_object = nullptr;
|
|
}
|
|
|
|
if (_main_object != nullptr) {
|
|
nout << "panda_script_object ref = "
|
|
<< _main_object->_ref_count << "\n";
|
|
_main_object->set_instance(nullptr);
|
|
P3D_OBJECT_DECREF(_main_object);
|
|
_main_object = nullptr;
|
|
}
|
|
|
|
Downloads::iterator di;
|
|
for (di = _downloads.begin(); di != _downloads.end(); ++di) {
|
|
P3DDownload *download = (*di).second;
|
|
if (download->get_instance() == this) {
|
|
download->set_instance(nullptr);
|
|
}
|
|
p3d_unref_delete(download);
|
|
}
|
|
_downloads.clear();
|
|
|
|
DESTROY_LOCK(_request_lock);
|
|
|
|
// TODO: Is it possible for someone to delete an instance while a download
|
|
// is still running? Who will crash when this happens?
|
|
}
|
|
|
|
/**
|
|
* Invalidates the instance and removes any structures prior to deleting.
|
|
*/
|
|
void P3DInstance::
|
|
cleanup() {
|
|
_failed = true;
|
|
|
|
if (_auth_session != nullptr) {
|
|
_auth_session->shutdown(false);
|
|
p3d_unref_delete(_auth_session);
|
|
_auth_session = nullptr;
|
|
}
|
|
|
|
for (int i = 0; i < (int)IT_num_image_types; ++i) {
|
|
_image_files[i].cleanup();
|
|
}
|
|
|
|
// Tell all of the packages that we're no longer in business for them.
|
|
Packages::iterator pi;
|
|
for (pi = _packages.begin(); pi != _packages.end(); ++pi) {
|
|
(*pi)->remove_instance(this);
|
|
}
|
|
_packages.clear();
|
|
if (_image_package != nullptr) {
|
|
_image_package->remove_instance(this);
|
|
_image_package = nullptr;
|
|
}
|
|
|
|
if (_certlist_package != nullptr) {
|
|
_certlist_package->remove_instance(this);
|
|
_certlist_package = nullptr;
|
|
}
|
|
|
|
if (_p3dcert_package != nullptr) {
|
|
_p3dcert_package->remove_instance(this);
|
|
_p3dcert_package = nullptr;
|
|
}
|
|
|
|
if (_splash_window != nullptr) {
|
|
delete _splash_window;
|
|
_splash_window = nullptr;
|
|
}
|
|
|
|
if (_temp_p3d_filename != nullptr) {
|
|
delete _temp_p3d_filename;
|
|
_temp_p3d_filename = nullptr;
|
|
}
|
|
|
|
if (_xpackage != nullptr) {
|
|
delete _xpackage;
|
|
_xpackage = nullptr;
|
|
}
|
|
|
|
#ifdef __APPLE__
|
|
if (_frame_timer != nullptr) {
|
|
CFRunLoopTimerInvalidate(_frame_timer);
|
|
CFRelease(_frame_timer);
|
|
_frame_timer = nullptr;
|
|
}
|
|
|
|
free_swbuffer();
|
|
#endif
|
|
|
|
TiXmlDocument *doc = nullptr;
|
|
ACQUIRE_LOCK(_request_lock);
|
|
RawRequests::iterator ri;
|
|
for (ri = _raw_requests.begin(); ri != _raw_requests.end(); ++ri) {
|
|
doc = (*ri);
|
|
delete doc;
|
|
}
|
|
_raw_requests.clear();
|
|
RELEASE_LOCK(_request_lock);
|
|
|
|
BakedRequests::iterator bi;
|
|
for (bi = _baked_requests.begin(); bi != _baked_requests.end(); ++bi) {
|
|
P3D_request *request = (*bi);
|
|
finish_request(request, false);
|
|
}
|
|
_baked_requests.clear();
|
|
|
|
Downloads::iterator di;
|
|
for (di = _downloads.begin(); di != _downloads.end(); ++di) {
|
|
P3DDownload *download = (*di).second;
|
|
download->cancel();
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Specifies a URL that should be contacted to download the instance data.
|
|
* Normally this, or set_p3d_filename() or make_p3d_stream(), is only called
|
|
* once.
|
|
*
|
|
* The instance data at the other end of this URL is key. We can't start the
|
|
* instance until we have downloaded the instance file and examined the
|
|
* p3d_info.xml, and we know what Python version we need and so forth.
|
|
*/
|
|
void P3DInstance::
|
|
set_p3d_url(const string &p3d_url) {
|
|
if (p3d_url.empty()) {
|
|
nout << "No p3d URL specified. Cannot run.\n";
|
|
set_failed();
|
|
return;
|
|
}
|
|
_fparams.set_p3d_url(p3d_url);
|
|
|
|
// Save the last part of the URL as the p3d_basename, for reporting purposes
|
|
// or whatever.
|
|
determine_p3d_basename(p3d_url);
|
|
|
|
// Make a temporary file to receive the instance data.
|
|
assert(_temp_p3d_filename == nullptr);
|
|
_temp_p3d_filename = new P3DTemporaryFile(".p3d");
|
|
_stuff_to_download = true;
|
|
|
|
// Maybe it's time to open a splash window now.
|
|
make_splash_window();
|
|
|
|
// Mark the time we started downloading, so we'll know when to reveal the
|
|
// progress bar, and we can predict the total download time.
|
|
#ifdef _WIN32
|
|
_start_dl_tick = GetTickCount();
|
|
#else
|
|
gettimeofday(&_start_dl_timeval, nullptr);
|
|
#endif
|
|
_show_dl_instance_progress = false;
|
|
|
|
// Start downloading the data.
|
|
InstanceDownload *download = new InstanceDownload(this);
|
|
download->set_url(p3d_url);
|
|
download->set_filename(_temp_p3d_filename->get_filename());
|
|
if (_fparams.has_token("p3d_size")) {
|
|
download->set_total_expected_data(_fparams.lookup_token_int("p3d_size"));
|
|
}
|
|
|
|
_main_object->set_string_property("status", "downloading_instance");
|
|
start_download(download);
|
|
}
|
|
|
|
/**
|
|
* Indicates an intention to transmit the p3d data as a stream. Should return
|
|
* a new unique stream ID to receive it.
|
|
*/
|
|
int P3DInstance::
|
|
make_p3d_stream(const string &p3d_url) {
|
|
_fparams.set_p3d_url(p3d_url);
|
|
|
|
// Save the last part of the URL as the p3d_basename, for reporting purposes
|
|
// or whatever.
|
|
determine_p3d_basename(p3d_url);
|
|
|
|
// Make a temporary file to receive the instance data.
|
|
assert(_temp_p3d_filename == nullptr);
|
|
_temp_p3d_filename = new P3DTemporaryFile(".p3d");
|
|
_stuff_to_download = true;
|
|
|
|
// Maybe it's time to open a splash window now.
|
|
make_splash_window();
|
|
|
|
// Mark the time we started downloading, so we'll know when to reveal the
|
|
// progress bar.
|
|
#ifdef _WIN32
|
|
_start_dl_tick = GetTickCount();
|
|
#else
|
|
gettimeofday(&_start_dl_timeval, nullptr);
|
|
#endif
|
|
_show_dl_instance_progress = false;
|
|
|
|
// Start downloading the data.
|
|
InstanceDownload *download = new InstanceDownload(this);
|
|
download->set_url(p3d_url);
|
|
download->set_filename(_temp_p3d_filename->get_filename());
|
|
if (_fparams.has_token("p3d_size")) {
|
|
download->set_total_expected_data(_fparams.lookup_token_int("p3d_size"));
|
|
}
|
|
|
|
_main_object->set_string_property("status", "downloading_instance");
|
|
return start_download(download, false);
|
|
}
|
|
|
|
/**
|
|
* Specifies the file that contains the instance data. Normally this is only
|
|
* called once.
|
|
*/
|
|
void P3DInstance::
|
|
set_p3d_filename(const string &p3d_filename, const int &p3d_offset) {
|
|
determine_p3d_basename(p3d_filename);
|
|
priv_set_p3d_filename(p3d_filename, p3d_offset);
|
|
}
|
|
|
|
/**
|
|
* Changes the window parameters, e.g. to resize or reposition the window; or
|
|
* sets the parameters for the first time, creating the initial window.
|
|
*/
|
|
void P3DInstance::
|
|
set_wparams(const P3DWindowParams &wparams) {
|
|
bool prev_got_wparams = _got_wparams;
|
|
_got_wparams = true;
|
|
_wparams = wparams;
|
|
|
|
nout << "set_wparams: " << wparams.get_window_type()
|
|
<< " " << wparams.get_win_width() << " " << wparams.get_win_height()
|
|
<< "\n";
|
|
|
|
if (_hidden || _wparams.get_win_width() == 0 || _wparams.get_win_height() == 0) {
|
|
// If we're a hidden app, or if the window has no size, then it is really
|
|
// a hidden window, regardless of what type it claims to be.
|
|
_wparams.set_window_type(P3D_WT_hidden);
|
|
}
|
|
|
|
if (_wparams.get_window_type() != P3D_WT_hidden) {
|
|
// Update or create the splash window.
|
|
if (_splash_window != nullptr) {
|
|
_splash_window->set_wparams(_wparams);
|
|
} else {
|
|
make_splash_window();
|
|
}
|
|
}
|
|
|
|
// It doesn't make much sense to go further than this point if the instance
|
|
// is already in the failed state.
|
|
if (is_failed()) {
|
|
return;
|
|
}
|
|
|
|
if (_wparams.get_window_type() != P3D_WT_hidden) {
|
|
#ifdef __APPLE__
|
|
// On Mac, we have to communicate the results of the rendering back via
|
|
// shared memory, instead of directly parenting windows to the browser.
|
|
// Set up this mechanism.
|
|
int x_size = _wparams.get_win_width();
|
|
int y_size = _wparams.get_win_height();
|
|
if (x_size != 0 && y_size != 0) {
|
|
if (_swbuffer == nullptr || _swbuffer->get_x_size() != x_size ||
|
|
_swbuffer->get_y_size() != y_size) {
|
|
// We need to open a new shared buffer.
|
|
alloc_swbuffer();
|
|
}
|
|
|
|
if (_swbuffer == nullptr) {
|
|
nout << "Could not open swbuffer\n";
|
|
}
|
|
}
|
|
#endif // __APPLE__
|
|
}
|
|
|
|
// Update the instance in the sub-process.
|
|
if (_session != nullptr) {
|
|
TiXmlDocument *doc = new TiXmlDocument;
|
|
TiXmlElement *xcommand = new TiXmlElement("command");
|
|
xcommand->SetAttribute("cmd", "setup_window");
|
|
xcommand->SetAttribute("instance_id", get_instance_id());
|
|
TiXmlElement *xwparams = _wparams.make_xml(this);
|
|
|
|
doc->LinkEndChild(xcommand);
|
|
xcommand->LinkEndChild(xwparams);
|
|
|
|
_session->send_command(doc);
|
|
}
|
|
|
|
if (!prev_got_wparams) {
|
|
// If this was the first set_wparams call, try to start the app.
|
|
if (_p3d_trusted && get_packages_ready()) {
|
|
ready_to_start();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns a pointer to the top-level scriptable object of the instance, to be
|
|
* used by JavaScript code in the browser to control this program.
|
|
*/
|
|
P3D_object *P3DInstance::
|
|
get_panda_script_object() const {
|
|
nout << "get_panda_script_object\n";
|
|
return _main_object;
|
|
}
|
|
|
|
/**
|
|
* Stores a pointer to the top-level window object of the browser, to be used
|
|
* by Panda code to control JavaScript. The new object's reference count is
|
|
* incremented, and the previous object's is decremented.
|
|
*/
|
|
void P3DInstance::
|
|
set_browser_script_object(P3D_object *browser_script_object) {
|
|
nout << "set_browser_script_object\n";
|
|
if (browser_script_object != _dom_object) {
|
|
P3D_OBJECT_XDECREF(_dom_object);
|
|
_dom_object = browser_script_object;
|
|
if (_dom_object != nullptr) {
|
|
P3D_OBJECT_INCREF(_dom_object);
|
|
}
|
|
|
|
if (_session != nullptr) {
|
|
send_browser_script_object();
|
|
}
|
|
}
|
|
|
|
// Query the origin: protocol, hostname, and port. We'll use this to limit
|
|
// access to the scripting interfaces for a particular p3d file.
|
|
_origin_protocol.clear();
|
|
_origin_hostname.clear();
|
|
_origin_port.clear();
|
|
if (_dom_object != nullptr) {
|
|
P3D_object *location = P3D_OBJECT_GET_PROPERTY(_dom_object, "location");
|
|
if (location != nullptr) {
|
|
P3D_object *protocol = P3D_OBJECT_GET_PROPERTY(location, "protocol");
|
|
if (protocol != nullptr) {
|
|
int size = P3D_OBJECT_GET_STRING(protocol, nullptr, 0);
|
|
char *buffer = new char[size];
|
|
P3D_OBJECT_GET_STRING(protocol, buffer, size);
|
|
_origin_protocol = string(buffer, size);
|
|
delete [] buffer;
|
|
P3D_OBJECT_DECREF(protocol);
|
|
}
|
|
|
|
P3D_object *hostname = P3D_OBJECT_GET_PROPERTY(location, "hostname");
|
|
if (hostname != nullptr) {
|
|
int size = P3D_OBJECT_GET_STRING(hostname, nullptr, 0);
|
|
char *buffer = new char[size];
|
|
P3D_OBJECT_GET_STRING(hostname, buffer, size);
|
|
_origin_hostname = string(buffer, size);
|
|
delete [] buffer;
|
|
P3D_OBJECT_DECREF(hostname);
|
|
}
|
|
|
|
P3D_object *port = P3D_OBJECT_GET_PROPERTY(location, "port");
|
|
if (port != nullptr) {
|
|
int size = P3D_OBJECT_GET_STRING(port, nullptr, 0);
|
|
char *buffer = new char[size];
|
|
P3D_OBJECT_GET_STRING(port, buffer, size);
|
|
_origin_port = string(buffer, size);
|
|
delete [] buffer;
|
|
P3D_OBJECT_DECREF(port);
|
|
}
|
|
|
|
if (_origin_hostname.empty() && _origin_protocol == "file:") {
|
|
_origin_hostname = "localhost";
|
|
}
|
|
|
|
if (_origin_port.empty()) {
|
|
// Maybe the actual URL doesn't include the port, in which case it is
|
|
// implicit.
|
|
if (_origin_protocol == "http:") {
|
|
_origin_port = "80";
|
|
} else if (_origin_protocol == "https:") {
|
|
_origin_port = "443";
|
|
}
|
|
}
|
|
|
|
P3D_OBJECT_DECREF(location);
|
|
}
|
|
}
|
|
|
|
nout << "origin is " << _origin_protocol << "//" << _origin_hostname;
|
|
if (!_origin_port.empty()) {
|
|
nout << ":" << _origin_port;
|
|
}
|
|
nout << "\n";
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns true if the instance has any pending requests at the time of this
|
|
* call, false otherwise.
|
|
*/
|
|
bool P3DInstance::
|
|
has_request() {
|
|
return _request_pending;
|
|
}
|
|
|
|
/**
|
|
* Returns a newly-allocated P3D_request corresponding to the pending request
|
|
* for the host, or NULL if there is no pending request. If the return value
|
|
* is non-NULL, it should eventually be passed back to finish_request() for
|
|
* cleanup.
|
|
*/
|
|
P3D_request *P3DInstance::
|
|
get_request() {
|
|
bake_requests();
|
|
if (_baked_requests.empty()) {
|
|
// No requests ready.
|
|
_request_pending = false;
|
|
return nullptr;
|
|
}
|
|
|
|
P3D_request *request = _baked_requests.front();
|
|
_baked_requests.pop_front();
|
|
_request_pending = !_baked_requests.empty();
|
|
|
|
if (request != nullptr) {
|
|
switch (request->_request_type) {
|
|
case P3D_RT_notify:
|
|
{
|
|
// Also eval the associated HTML token, if any.
|
|
string message = request->_request._notify._message;
|
|
string expression = _fparams.lookup_token(message);
|
|
nout << "notify: " << message << " " << expression << "\n";
|
|
if (!expression.empty() && _dom_object != nullptr) {
|
|
P3D_object *result = P3D_OBJECT_EVAL(_dom_object, expression.c_str());
|
|
P3D_OBJECT_XDECREF(result);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case P3D_RT_stop:
|
|
{
|
|
// We also send an implicit message when Python requests itself to
|
|
// shutdown.
|
|
_main_object->set_pyobj(nullptr);
|
|
_main_object->set_string_property("status", "stopped");
|
|
|
|
string message = "onpythonstop";
|
|
string expression = _fparams.lookup_token(message);
|
|
nout << "notify: " << message << " " << expression << "\n";
|
|
if (!expression.empty() && _dom_object != nullptr) {
|
|
P3D_object *result = P3D_OBJECT_EVAL(_dom_object, expression.c_str());
|
|
P3D_OBJECT_XDECREF(result);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case P3D_RT_callback:
|
|
{
|
|
// And when the callback request is extracted, we make the callback.
|
|
P3D_callback_func *func = request->_request._callback._func;
|
|
void *data = request->_request._callback._data;
|
|
(*func)(data);
|
|
}
|
|
break;
|
|
|
|
case P3D_RT_forget_package:
|
|
{
|
|
// We're being asked to forget a particular package.
|
|
const char *host_url = request->_request._forget_package._host_url;
|
|
const char *package_name = request->_request._forget_package._package_name;
|
|
const char *package_version = request->_request._forget_package._package_version;
|
|
if (package_version == nullptr) {
|
|
package_version = "";
|
|
}
|
|
|
|
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
|
P3DHost *host = inst_mgr->get_host(host_url);
|
|
if (package_name != nullptr) {
|
|
P3DPackage *package = host->get_package(package_name, package_version, _session_platform, "");
|
|
host->forget_package(package);
|
|
} else {
|
|
// If a NULL package name is given, forget the whole host.
|
|
inst_mgr->forget_host(host);
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return request;
|
|
}
|
|
|
|
/**
|
|
* Copies requests from the _raw_requests queue, which is built up in one or
|
|
* more sub-threads, into the _baked_requests queue, which is publicly
|
|
* presented to the browser. Along the way, some requests (like script
|
|
* requests) are handled immediately.
|
|
*
|
|
* At the end of this call, _baked_requests will contain the current set of
|
|
* requests pending for the browser.
|
|
*
|
|
* This method should only be called in the main thread.
|
|
*/
|
|
void P3DInstance::
|
|
bake_requests() {
|
|
while (true) {
|
|
// Get the latest request from the read thread.
|
|
TiXmlDocument *doc = nullptr;
|
|
ACQUIRE_LOCK(_request_lock);
|
|
if (!_raw_requests.empty()) {
|
|
doc = _raw_requests.front();
|
|
_raw_requests.pop_front();
|
|
}
|
|
RELEASE_LOCK(_request_lock);
|
|
|
|
if (doc == nullptr) {
|
|
// No more requests to process right now.
|
|
return;
|
|
}
|
|
|
|
// Now we've got a request in XML form; convert it to P3D_request form.
|
|
TiXmlElement *xrequest = doc->FirstChildElement("request");
|
|
assert(xrequest != nullptr);
|
|
P3D_request *request = make_p3d_request(xrequest);
|
|
delete doc;
|
|
|
|
if (request != nullptr) {
|
|
_baked_requests.push_back(request);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* May be called in any thread to add a new XML request to the pending_request
|
|
* queue for this instance. The XML document will be deleted when the request
|
|
* is eventually handled.
|
|
*/
|
|
void P3DInstance::
|
|
add_raw_request(TiXmlDocument *doc) {
|
|
ACQUIRE_LOCK(_request_lock);
|
|
_raw_requests.push_back(doc);
|
|
_request_pending = true;
|
|
RELEASE_LOCK(_request_lock);
|
|
|
|
// We don't decode the XML yet, since we might be running in any thread
|
|
// here. We'll decode it in the main thread, where it's safe.
|
|
|
|
// Tell the world we've got a new request.
|
|
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
|
inst_mgr->signal_request_ready(this);
|
|
if (_session != nullptr) {
|
|
_session->signal_request_ready(this);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* May be called in the main thread only to add a new request to the
|
|
* baked_request queue. This request queue is directly passed on the browser
|
|
* without further processing at this level.
|
|
*/
|
|
void P3DInstance::
|
|
add_baked_request(P3D_request *request) {
|
|
assert(request->_instance == nullptr);
|
|
request->_instance = this;
|
|
ref();
|
|
|
|
_baked_requests.push_back(request);
|
|
_request_pending = true;
|
|
|
|
// Tell the world we've got a new request.
|
|
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
|
inst_mgr->signal_request_ready(this);
|
|
}
|
|
|
|
/**
|
|
* Deallocates a previously-returned request from get_request(). If handled
|
|
* is true, the request has been handled by the host; otherwise, it has been
|
|
* ignored.
|
|
*/
|
|
void P3DInstance::
|
|
finish_request(P3D_request *request, bool handled) {
|
|
assert(request != nullptr);
|
|
if (request->_instance == nullptr) {
|
|
nout << "Ignoring empty request " << request << "\n";
|
|
return;
|
|
}
|
|
|
|
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
|
if (inst_mgr->validate_instance(request->_instance) == nullptr) {
|
|
// nout << "Ignoring unknown request " << request << "\n";
|
|
return;
|
|
}
|
|
|
|
switch (request->_request_type) {
|
|
case P3D_RT_stop:
|
|
break;
|
|
|
|
case P3D_RT_get_url:
|
|
if (request->_request._get_url._url != nullptr) {
|
|
free((char *)request->_request._get_url._url);
|
|
request->_request._get_url._url = nullptr;
|
|
}
|
|
break;
|
|
|
|
case P3D_RT_notify:
|
|
if (request->_request._notify._message != nullptr) {
|
|
free((char *)request->_request._notify._message);
|
|
request->_request._notify._message = nullptr;
|
|
}
|
|
break;
|
|
|
|
case P3D_RT_forget_package:
|
|
if (request->_request._forget_package._host_url != nullptr) {
|
|
free((char *)request->_request._forget_package._host_url);
|
|
request->_request._forget_package._host_url = nullptr;
|
|
}
|
|
if (request->_request._forget_package._package_name != nullptr) {
|
|
free((char *)request->_request._forget_package._package_name);
|
|
request->_request._forget_package._package_name = nullptr;
|
|
}
|
|
if (request->_request._forget_package._package_version != nullptr) {
|
|
free((char *)request->_request._forget_package._package_version);
|
|
request->_request._forget_package._package_version = nullptr;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
p3d_unref_delete(((P3DInstance *)request->_instance));
|
|
request->_instance = nullptr;
|
|
|
|
delete request;
|
|
}
|
|
|
|
/**
|
|
* Called by the host in response to a get_url request, this sends the data
|
|
* retrieved from the requested URL, a piece at a time.
|
|
*/
|
|
bool P3DInstance::
|
|
feed_url_stream(int unique_id,
|
|
P3D_result_code result_code,
|
|
int http_status_code,
|
|
size_t total_expected_data,
|
|
const unsigned char *this_data,
|
|
size_t this_data_size) {
|
|
Downloads::iterator di = _downloads.find(unique_id);
|
|
if (di == _downloads.end()) {
|
|
nout << "Unexpected feed_url_stream for " << unique_id << "\n";
|
|
// Don't know this request.
|
|
return false;
|
|
}
|
|
|
|
P3DDownload *download = (*di).second;
|
|
assert(download->get_instance() == this);
|
|
bool download_ok = download->feed_url_stream
|
|
(result_code, http_status_code, total_expected_data,
|
|
this_data, this_data_size);
|
|
|
|
if (!download_ok || download->get_download_finished()) {
|
|
// All done.
|
|
if (download->get_instance() == this) {
|
|
download->set_instance(nullptr);
|
|
}
|
|
_downloads.erase(di);
|
|
p3d_unref_delete(download);
|
|
}
|
|
|
|
return download_ok;
|
|
}
|
|
|
|
/**
|
|
* Responds to the os-generated window event. Returns true if the event is
|
|
* handled, false if ignored.
|
|
*/
|
|
bool P3DInstance::
|
|
handle_event(const P3D_event_data &event) {
|
|
bool retval = false;
|
|
if (_splash_window != nullptr) {
|
|
if (_splash_window->handle_event(event)) {
|
|
retval = true;
|
|
}
|
|
}
|
|
|
|
#if defined(__APPLE__)
|
|
if (event._event_type == P3D_ET_osx_event_record) {
|
|
retval = handle_event_osx_event_record(event);
|
|
} else if (event._event_type == P3D_ET_osx_cocoa) {
|
|
retval = handle_event_osx_cocoa(event);
|
|
} else {
|
|
assert(false);
|
|
}
|
|
#endif // __APPLE__
|
|
|
|
return retval;
|
|
}
|
|
|
|
/**
|
|
* Returns the log filename for this particular session, if the session was
|
|
* started and if it has a log file. Returns empty string if the session
|
|
* never started or if it lacks a log file.
|
|
*
|
|
* This is the same value returned by P3DSession::get_log_pathname(), except
|
|
* that it remains valid even after the session has closed.
|
|
*/
|
|
const string &P3DInstance::
|
|
get_log_pathname() const {
|
|
if (_session != nullptr) {
|
|
return _session->_log_pathname;
|
|
}
|
|
return _log_pathname;
|
|
}
|
|
|
|
/**
|
|
* Adds the package to the list of packages used by this instance. The
|
|
* instance will share responsibility for downloading the package with any of
|
|
* the other instances that use the same package.
|
|
*
|
|
* The seq value should be the expected minimum package_seq value for the
|
|
* indicated package. If the given seq value is higher than the package_seq
|
|
* value in the contents.xml file cached for the host, it is a sign that the
|
|
* contents.xml file is out of date and needs to be redownloaded.
|
|
*/
|
|
void P3DInstance::
|
|
add_package(const string &name, const string &version, const string &seq,
|
|
P3DHost *host) {
|
|
string alt_host = _fparams.lookup_token("alt_host");
|
|
|
|
// Look up in the p3d_info.xml file to see if this p3d file has a specific
|
|
// alt_host indication for this host_url.
|
|
string alt_host_url = find_alt_host_url(host->get_host_url(), alt_host);
|
|
if (!alt_host_url.empty()) {
|
|
// If it does, we go ahead and switch to that host now, instead of
|
|
// bothering to contact the original host.
|
|
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
|
host = inst_mgr->get_host(alt_host_url);
|
|
alt_host.clear();
|
|
}
|
|
|
|
if (!host->has_contents_file()) {
|
|
// Since we haven't downloaded this host's contents.xml file yet, get its
|
|
// additional host information.
|
|
get_host_info(host);
|
|
}
|
|
|
|
P3DPackage *package = host->get_package(name, version, _session_platform,
|
|
seq, alt_host);
|
|
add_package(package);
|
|
}
|
|
|
|
/**
|
|
* Adds the package to the list of packages used by this instance. The
|
|
* instance will share responsibility for downloading the package with any of
|
|
* the other instances that use the same package.
|
|
*/
|
|
void P3DInstance::
|
|
add_package(P3DPackage *package) {
|
|
if (find(_packages.begin(), _packages.end(), package) != _packages.end()) {
|
|
// Already have this package.
|
|
return;
|
|
}
|
|
|
|
if (package->get_package_name() == "panda3d") {
|
|
_panda3d_package = package;
|
|
}
|
|
|
|
_packages.push_back(package);
|
|
|
|
// This call must be at the end of this method, because it might ultimately
|
|
// start the application before it returns (if this was the last required
|
|
// package).
|
|
package->add_instance(this);
|
|
}
|
|
|
|
/**
|
|
* Indicates that the given package is destructing and this instance should no
|
|
* longer retain a pointer to it. This is normally called only by the
|
|
* P3DPackage destructor, and it invalidates the instance.
|
|
*/
|
|
void P3DInstance::
|
|
remove_package(P3DPackage *package) {
|
|
Packages::iterator pi = find(_packages.begin(), _packages.end(), package);
|
|
if (pi != _packages.end()) {
|
|
_packages.erase(pi);
|
|
}
|
|
pi = find(_downloading_packages.begin(), _downloading_packages.end(), package);
|
|
if (pi != _downloading_packages.end()) {
|
|
_downloading_packages.erase(pi);
|
|
}
|
|
if (package == _image_package) {
|
|
_image_package = nullptr;
|
|
}
|
|
if (package == _certlist_package) {
|
|
_certlist_package = nullptr;
|
|
}
|
|
if (package == _p3dcert_package) {
|
|
_p3dcert_package = nullptr;
|
|
}
|
|
if (package == _panda3d_package) {
|
|
_panda3d_package = nullptr;
|
|
}
|
|
|
|
set_failed();
|
|
}
|
|
|
|
/**
|
|
* Returns true if all of the packages required by the instance have their
|
|
* information available and are ready to be downloaded, false if one or more
|
|
* of them is still waiting for information (or has failed).
|
|
*/
|
|
bool P3DInstance::
|
|
get_packages_info_ready() const {
|
|
if (!_packages_specified) {
|
|
// We haven't even specified the full set of required packages yet.
|
|
return false;
|
|
}
|
|
|
|
Packages::const_iterator pi;
|
|
for (pi = _packages.begin(); pi != _packages.end(); ++pi) {
|
|
if (!(*pi)->get_info_ready()) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Returns true if all of the packages required by the instance (as specified
|
|
* in previous calls to add_package()) have been fully downloaded and are
|
|
* ready to run, or false if one or more of them still requires downloading.
|
|
*/
|
|
bool P3DInstance::
|
|
get_packages_ready() const {
|
|
if (!_packages_specified) {
|
|
// We haven't even specified the full set of required packages yet.
|
|
return false;
|
|
}
|
|
|
|
Packages::const_iterator pi;
|
|
for (pi = _packages.begin(); pi != _packages.end(); ++pi) {
|
|
if (!(*pi)->get_ready()) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Returns true if any of the packages required by the instance have failed to
|
|
* download (and thus we will never be ready).
|
|
*/
|
|
bool P3DInstance::
|
|
get_packages_failed() const {
|
|
Packages::const_iterator pi;
|
|
for (pi = _packages.begin(); pi != _packages.end(); ++pi) {
|
|
if ((*pi)->get_failed()) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Adds a newly-allocated P3DDownload object to the download queue, and issues
|
|
* the request to start it downloading. As the download data comes in, it
|
|
* will be fed to the download object.
|
|
*
|
|
* This increments the P3DDownload object's reference count, and will
|
|
* decrement it (and possibly delete the object) after download_finished() has
|
|
* been called.
|
|
*
|
|
* add_request should be true to actually request the URL from the plugin, or
|
|
* false not to. Normally, this should always be set true, except in the one
|
|
* special case of make_p3d_stream(), in which case the plugin is already
|
|
* prepared to send the stream and doesn't need to have it requested.
|
|
*
|
|
* Returns the unique ID of this stream.
|
|
*/
|
|
int P3DInstance::
|
|
start_download(P3DDownload *download, bool add_request) {
|
|
assert(download->get_download_id() == 0);
|
|
assert(!download->get_url().empty());
|
|
|
|
if (is_failed()) {
|
|
// Can't download anything more after failure.
|
|
download->cancel();
|
|
return 0;
|
|
}
|
|
|
|
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
|
|
|
int download_id = inst_mgr->get_unique_id();
|
|
download->set_download_id(download_id);
|
|
download->set_instance(this);
|
|
|
|
download->ref();
|
|
bool inserted = _downloads.insert(Downloads::value_type(download_id, download)).second;
|
|
assert(inserted);
|
|
|
|
// add_request will be false only for the initial p3d stream, which the
|
|
// plugin already knows about. For all other download streams, add_request
|
|
// is true in order to ask the plugin for the stream.
|
|
if (add_request) {
|
|
P3D_request *request = new P3D_request;
|
|
request->_instance = nullptr;
|
|
request->_request_type = P3D_RT_get_url;
|
|
request->_request._get_url._url = strdup(download->get_url().c_str());
|
|
request->_request._get_url._unique_id = download_id;
|
|
|
|
add_baked_request(request);
|
|
}
|
|
|
|
return download_id;
|
|
}
|
|
|
|
/**
|
|
* Asks the host to shut down this particular instance, presumably because the
|
|
* user has indicated it should exit. This call may be made in any thread.
|
|
*/
|
|
void P3DInstance::
|
|
request_stop_sub_thread() {
|
|
// Atomically check _requested_stop.
|
|
bool add_request = false;
|
|
ACQUIRE_LOCK(_request_lock);
|
|
if (!_requested_stop) {
|
|
_requested_stop = true;
|
|
add_request = true;
|
|
}
|
|
RELEASE_LOCK(_request_lock);
|
|
|
|
// If we haven't requested a stop already, do it now.
|
|
if (add_request) {
|
|
TiXmlDocument *doc = new TiXmlDocument;
|
|
TiXmlElement *xrequest = new TiXmlElement("request");
|
|
xrequest->SetAttribute("rtype", "stop");
|
|
doc->LinkEndChild(xrequest);
|
|
|
|
add_raw_request(doc);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Asks the host to shut down this particular instance, presumably because the
|
|
* user has indicated it should exit. This call may only be made in the main
|
|
* thread.
|
|
*/
|
|
void P3DInstance::
|
|
request_stop_main_thread() {
|
|
// Atomically check _requested_stop.
|
|
bool add_request = false;
|
|
ACQUIRE_LOCK(_request_lock);
|
|
if (!_requested_stop) {
|
|
_requested_stop = true;
|
|
add_request = true;
|
|
}
|
|
RELEASE_LOCK(_request_lock);
|
|
|
|
// If we haven't requested a stop already, do it now.
|
|
if (add_request) {
|
|
_requested_stop = true;
|
|
P3D_request *request = new P3D_request;
|
|
request->_instance = nullptr;
|
|
request->_request_type = P3D_RT_stop;
|
|
add_baked_request(request);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Asks the host to refresh the plugin window. This is only relevant for
|
|
* windowless plugins, for instance, the way OSX plugins always run.
|
|
*/
|
|
void P3DInstance::
|
|
request_refresh() {
|
|
P3D_request *request = new P3D_request;
|
|
request->_instance = nullptr;
|
|
request->_request_type = P3D_RT_refresh;
|
|
add_baked_request(request);
|
|
}
|
|
|
|
/**
|
|
* Asks the host to make a callback later.
|
|
*/
|
|
void P3DInstance::
|
|
request_callback(P3D_callback_func *func, void *data) {
|
|
P3D_request *request = new P3D_request;
|
|
request->_instance = nullptr;
|
|
request->_request_type = P3D_RT_callback;
|
|
request->_request._callback._func = func;
|
|
request->_request._callback._data = data;
|
|
add_baked_request(request);
|
|
}
|
|
|
|
/**
|
|
* Returns a newly-allocated XML structure that corresponds to the data within
|
|
* this instance.
|
|
*/
|
|
TiXmlElement *P3DInstance::
|
|
make_xml() {
|
|
assert(_got_fparams);
|
|
|
|
TiXmlElement *xinstance = new TiXmlElement("instance");
|
|
xinstance->SetAttribute("instance_id", _instance_id);
|
|
|
|
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
|
xinstance->SetAttribute("root_dir", inst_mgr->get_root_dir());
|
|
xinstance->SetAttribute("log_directory", inst_mgr->get_log_directory());
|
|
xinstance->SetAttribute("verify_contents", (int)inst_mgr->get_verify_contents());
|
|
|
|
// Tell the Panda process that it was started by a plugin that knows about
|
|
// the new per_platform flag.
|
|
xinstance->SetAttribute("respect_per_platform", 1);
|
|
|
|
if (!inst_mgr->get_super_mirror().empty()) {
|
|
xinstance->SetAttribute("super_mirror", inst_mgr->get_super_mirror());
|
|
}
|
|
|
|
|
|
TiXmlElement *xfparams = _fparams.make_xml();
|
|
xinstance->LinkEndChild(xfparams);
|
|
|
|
if (_got_wparams) {
|
|
TiXmlElement *xwparams = _wparams.make_xml(this);
|
|
xinstance->LinkEndChild(xwparams);
|
|
}
|
|
|
|
Packages::const_iterator pi;
|
|
for (pi = _packages.begin(); pi != _packages.end(); ++pi) {
|
|
TiXmlElement *xpackage = (*pi)->make_xml();
|
|
xinstance->LinkEndChild(xpackage);
|
|
}
|
|
|
|
TiXmlElement *xmain = _session->p3dobj_to_xml(_main_object);
|
|
xmain->SetValue("main");
|
|
xinstance->LinkEndChild(xmain);
|
|
|
|
return xinstance;
|
|
}
|
|
|
|
/**
|
|
* Called by the P3DSplashWindow code (maybe in a sub-thread) when the user
|
|
* clicks the button visible on the splash window. This will forward the
|
|
* event to the main thread via the request callback mechanism.
|
|
*/
|
|
void P3DInstance::
|
|
splash_button_clicked_sub_thread() {
|
|
TiXmlDocument *doc = new TiXmlDocument;
|
|
TiXmlElement *xrequest = new TiXmlElement("request");
|
|
xrequest->SetAttribute("rtype", "notify");
|
|
xrequest->SetAttribute("message", "buttonclick");
|
|
doc->LinkEndChild(xrequest);
|
|
|
|
add_raw_request(doc);
|
|
}
|
|
|
|
/**
|
|
* Called only in the main thread, indirectly from
|
|
* splash_button_clicked_sub_thread(), as the result of the user clicking on
|
|
* the button visible in the splash window.
|
|
*/
|
|
void P3DInstance::
|
|
splash_button_clicked_main_thread() {
|
|
if (is_failed()) {
|
|
// Can't click the button after we've failed.
|
|
nout << "Ignoring click for failed instance\n";
|
|
return;
|
|
}
|
|
|
|
if (!_p3d_trusted) {
|
|
auth_button_clicked();
|
|
} else if (_session == nullptr) {
|
|
play_button_clicked();
|
|
} else {
|
|
nout << "Ignoring click for already-started instance\n";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called to authorize the p3d file by the user clicking the red "auth"
|
|
* button.
|
|
*/
|
|
void P3DInstance::
|
|
auth_button_clicked() {
|
|
// Delete the previous session and create a new one.
|
|
if (_auth_session != nullptr) {
|
|
_auth_session->shutdown(false);
|
|
p3d_unref_delete(_auth_session);
|
|
_auth_session = nullptr;
|
|
}
|
|
|
|
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
|
_auth_session = inst_mgr->authorize_instance(this);
|
|
_auth_session->ref();
|
|
}
|
|
|
|
/**
|
|
* Called to start the game by the user clicking the green "play" button, or
|
|
* by JavaScript calling play().
|
|
*/
|
|
void P3DInstance::
|
|
play_button_clicked() {
|
|
if (_session == nullptr && _p3d_trusted) {
|
|
set_button_image(IT_none);
|
|
if (!_download_started) {
|
|
// Now we initiate the download.
|
|
_auto_install = true;
|
|
_auto_start = true;
|
|
if (get_packages_info_ready()) {
|
|
ready_to_install();
|
|
}
|
|
|
|
} else {
|
|
set_background_image(IT_launch);
|
|
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
|
inst_mgr->start_instance(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called by the P3DAuthSession code in a sub-thread when the auth dialog
|
|
* exits (for instance, because the user approved the certificate, or
|
|
* cancelled).
|
|
*/
|
|
void P3DInstance::
|
|
auth_finished_sub_thread() {
|
|
TiXmlDocument *doc = new TiXmlDocument;
|
|
TiXmlElement *xrequest = new TiXmlElement("request");
|
|
xrequest->SetAttribute("rtype", "notify");
|
|
xrequest->SetAttribute("message", "authfinished");
|
|
doc->LinkEndChild(xrequest);
|
|
|
|
add_raw_request(doc);
|
|
}
|
|
|
|
/**
|
|
* Called only in the main thread, indirectly from auth_finished_sub_thread(),
|
|
* as the result of the user closing the auth dialog.
|
|
*/
|
|
void P3DInstance::
|
|
auth_finished_main_thread() {
|
|
// Set this flag to indicate that the user has clicked on the red "auth"
|
|
// button. This eliminates the need to click on the green "start" button.
|
|
_auth_button_clicked = true;
|
|
|
|
// After the authorization program has returned, check the signature again.
|
|
check_p3d_signature();
|
|
}
|
|
|
|
/**
|
|
* Stops the instance (if it is running) and deletes any packages referenced
|
|
* by the instance. This is normally called by JavaScript, via
|
|
* P3DMainObject::call_uninstall().
|
|
*/
|
|
bool P3DInstance::
|
|
uninstall_packages() {
|
|
if (_packages.empty()) {
|
|
// If we have no packages (for instance, because we're untrusted), we
|
|
// can't uninstall anything.
|
|
nout << "Uninstall failed: no packages.\n";
|
|
return false;
|
|
}
|
|
|
|
nout << "Uninstalling " << _packages.size() << " packages\n";
|
|
|
|
Packages::const_iterator pi;
|
|
for (pi = _packages.begin(); pi != _packages.end(); ++pi) {
|
|
P3DPackage *package = (*pi);
|
|
if (package != _image_package && package != _certlist_package &&
|
|
package != _p3dcert_package) {
|
|
package->uninstall();
|
|
}
|
|
}
|
|
|
|
// Also clean up the start directory, if we have a custom start dir. We
|
|
// won't do this if verify_contents is 'none'.
|
|
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
|
if (inst_mgr->get_verify_contents() != P3D_VC_never) {
|
|
string start_dir_suffix = get_start_dir_suffix();
|
|
if (!start_dir_suffix.empty()) {
|
|
string start_dir = inst_mgr->get_start_dir() + start_dir_suffix;
|
|
nout << "Cleaning up start directory " << start_dir << "\n";
|
|
inst_mgr->delete_directory_recursively(start_dir);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Stops the instance (if it is running) and deletes all packages downloaded
|
|
* from any of the host(s) referenced by the instance. This is a more
|
|
* aggressive uninstall than uninstall_packages(). This is normally called by
|
|
* JavaScript, via P3DMainObject::call_uninstall().
|
|
*/
|
|
bool P3DInstance::
|
|
uninstall_host() {
|
|
if (_packages.empty()) {
|
|
// If we have no packages (for instance, because we're untrusted), we
|
|
// can't uninstall anything.
|
|
nout << "Uninstall failed: no packages.\n";
|
|
return false;
|
|
}
|
|
|
|
uninstall_packages();
|
|
|
|
// Collect the set of hosts referenced by this instance.
|
|
std::set<P3DHost *> hosts;
|
|
Packages::const_iterator pi;
|
|
for (pi = _packages.begin(); pi != _packages.end(); ++pi) {
|
|
P3DPackage *package = (*pi);
|
|
if (package != _image_package && package != _certlist_package &&
|
|
package != _p3dcert_package) {
|
|
hosts.insert(package->get_host());
|
|
}
|
|
}
|
|
nout << "Uninstalling " << hosts.size() << " hosts\n";
|
|
|
|
// Uninstall all of them.
|
|
std::set<P3DHost *>::iterator hi;
|
|
for (hi = hosts.begin(); hi != hosts.end(); ++hi) {
|
|
P3DHost *host = (*hi);
|
|
host->uninstall();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* The private implementation of set_p3d_filename(), this does all the work
|
|
* except for updating p3d_basename. It is intended to be called internally,
|
|
* and might be passed a temporary filename.
|
|
*/
|
|
void P3DInstance::
|
|
priv_set_p3d_filename(const string &p3d_filename, const int &p3d_offset) {
|
|
if (!_fparams.get_p3d_filename().empty()) {
|
|
nout << "p3d_filename already set to: " << _fparams.get_p3d_filename()
|
|
<< ", trying to set to " << p3d_filename << "\n";
|
|
return;
|
|
}
|
|
|
|
_fparams.set_p3d_filename(p3d_filename);
|
|
// The default for p3d_offset is -1, which means not to change it.
|
|
if (p3d_offset >= 0) {
|
|
_fparams.set_p3d_offset(p3d_offset);
|
|
}
|
|
_got_fparams = true;
|
|
|
|
_main_object->set_float_property("instanceDownloadProgress", 1.0);
|
|
|
|
// Generate a special notification: onpluginload, indicating the plugin has
|
|
// read its parameters and is ready to be queried (even if Python has not
|
|
// yet started).
|
|
send_notify("onpluginload");
|
|
|
|
if (!_mf_reader.open_read(_fparams.get_p3d_filename(), _fparams.get_p3d_offset())) {
|
|
if (_fparams.get_p3d_offset() == 0) {
|
|
nout << "Couldn't read " << _fparams.get_p3d_filename() << "\n";
|
|
} else {
|
|
nout << "Couldn't read " << _fparams.get_p3d_filename()
|
|
<< " at offset " << _fparams.get_p3d_offset() << "\n";
|
|
}
|
|
set_failed();
|
|
return;
|
|
}
|
|
|
|
check_p3d_signature();
|
|
}
|
|
|
|
/**
|
|
* Determines _p3d_basename from the indicated URL.
|
|
*/
|
|
void P3DInstance::
|
|
determine_p3d_basename(const string &p3d_url) {
|
|
string file_part = p3d_url;
|
|
size_t question = file_part.find('?');
|
|
if (question != string::npos) {
|
|
file_part = file_part.substr(0, question);
|
|
}
|
|
size_t slash = file_part.rfind('/');
|
|
if (slash != string::npos) {
|
|
file_part = file_part.substr(slash + 1);
|
|
}
|
|
_p3d_basename = file_part;
|
|
|
|
nout << "p3d_basename = " << _p3d_basename << "\n";
|
|
}
|
|
|
|
/**
|
|
* Returns true if the indicated origin_match string, one of either run_origin
|
|
* or script_origin from the p3d_info.xml file, matches the origin of the page
|
|
* that embedded the p3d file.
|
|
*/
|
|
bool P3DInstance::
|
|
check_matches_origin(const string &origin_match) {
|
|
// First, separate the string up at the semicolons.
|
|
size_t p = 0;
|
|
size_t semicolon = origin_match.find(';');
|
|
while (semicolon != string::npos) {
|
|
if (check_matches_origin_one(origin_match.substr(p, semicolon - p))) {
|
|
return true;
|
|
}
|
|
p = semicolon + 1;
|
|
semicolon = origin_match.find(';', p);
|
|
}
|
|
if (check_matches_origin_one(origin_match.substr(p))) {
|
|
return true;
|
|
}
|
|
|
|
// It doesn't match any of the semicolon-delimited strings within
|
|
// origin_match.
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Called for each semicolon-delimited string within origin_match passed to
|
|
* check_matches_origin().
|
|
*/
|
|
bool P3DInstance::
|
|
check_matches_origin_one(const string &origin_match) {
|
|
// Do we have a protocol?
|
|
size_t p = 0;
|
|
size_t colon = origin_match.find(':');
|
|
if (colon + 1 < origin_match.length() && origin_match[colon + 1] == '/') {
|
|
// Yes. It should therefore match the protocol we have in the origin.
|
|
string protocol = origin_match.substr(0, colon + 1);
|
|
if (!check_matches_component(_origin_protocol, protocol)) {
|
|
return false;
|
|
}
|
|
p = colon + 2;
|
|
// We'll support both http:hostname and http:hostname, in case the user is
|
|
// sloppy.
|
|
if (p < origin_match.length() && origin_match[p] == '/') {
|
|
++p;
|
|
}
|
|
colon = origin_match.find(':', p);
|
|
}
|
|
|
|
// Do we have a port?
|
|
if (colon < origin_match.length() && isdigit(origin_match[colon + 1])) {
|
|
// Yes. It should therefore match the port we have in the origin.
|
|
string port = origin_match.substr(colon + 1);
|
|
if (!check_matches_component(_origin_port, port)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// The hostname should also match what we have in the origin.
|
|
string hostname = origin_match.substr(p, colon - p);
|
|
if (!check_matches_hostname(_origin_hostname, hostname)) {
|
|
return false;
|
|
}
|
|
|
|
// Everything matches.
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Matches the hostname of check_matches_origin: the individual components of
|
|
* the hostname are matched independently, with '**.' allowed at the beginning
|
|
* to indicate zero or more prefixes. Returns true on match, false on
|
|
* failure.
|
|
*/
|
|
bool P3DInstance::
|
|
check_matches_hostname(const string &orig, const string &match) {
|
|
// First, separate both strings up at the dots.
|
|
vector<string> orig_components;
|
|
separate_components(orig_components, orig);
|
|
|
|
vector<string> match_components;
|
|
separate_components(match_components, match);
|
|
|
|
// If the first component of match is "**", it means we accept any number,
|
|
// zero or more, of components at the beginning of the hostname.
|
|
if (!match_components.empty() && match_components[0] == "**") {
|
|
// Remove the leading "**"
|
|
match_components.erase(match_components.begin());
|
|
// Then remove any extra components from the beginning of orig_components;
|
|
// we won't need to check them.
|
|
if (orig_components.size() > match_components.size()) {
|
|
size_t num_to_remove = orig_components.size() - match_components.size();
|
|
orig_components.erase(orig_components.begin(), orig_components.begin() + num_to_remove);
|
|
}
|
|
}
|
|
|
|
// Now match the remaining components one-to-one.
|
|
if (match_components.size() != orig_components.size()) {
|
|
return false;
|
|
}
|
|
|
|
vector<string>::const_iterator p = orig_components.begin();
|
|
vector<string>::const_iterator p2 = match_components.begin();
|
|
|
|
while (p != orig_components.end()) {
|
|
assert(p2 != match_components.end());
|
|
if (!check_matches_component(*p, *p2)) {
|
|
return false;
|
|
}
|
|
++p;
|
|
++p2;
|
|
}
|
|
|
|
assert(p2 == match_components.end());
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Separates the indicated hostname into its components at the dots.
|
|
*/
|
|
void P3DInstance::
|
|
separate_components(vector<string> &components, const string &str) {
|
|
size_t p = 0;
|
|
size_t dot = str.find('.');
|
|
while (dot != string::npos) {
|
|
components.push_back(str.substr(p, dot - p));
|
|
p = dot + 1;
|
|
dot = str.find('.', p);
|
|
}
|
|
components.push_back(str.substr(p));
|
|
}
|
|
|
|
/**
|
|
* Matches a single component of check_matches_origin: either protocol or
|
|
* port, or a single component of the hostname. Case-insensitive, and
|
|
* supports the '*' wildcard operator to match the entire component. Returns
|
|
* true on match, false on failure.
|
|
*/
|
|
bool P3DInstance::
|
|
check_matches_component(const string &orig, const string &match) {
|
|
if (match == "*") {
|
|
return true;
|
|
}
|
|
|
|
// Case-insensitive compare.
|
|
if (orig.length() != match.length()) {
|
|
return false;
|
|
}
|
|
|
|
string::const_iterator p = orig.begin();
|
|
string::const_iterator p2 = match.begin();
|
|
|
|
while (p != orig.end()) {
|
|
assert(p2 != match.end());
|
|
if (tolower(*p) != tolower(*p2)) {
|
|
return false;
|
|
}
|
|
++p;
|
|
++p2;
|
|
}
|
|
|
|
assert(p2 == match.end());
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Checks the signature(s) encoded in the p3d file, and looks to see if any of
|
|
* them are recognized.
|
|
*
|
|
* If the signature is recognized, calls mark_p3d_trusted(); otherwise, calls
|
|
* mark_p3d_untrusted().
|
|
*/
|
|
void P3DInstance::
|
|
check_p3d_signature() {
|
|
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
|
if (inst_mgr->get_trusted_environment()) {
|
|
// If we're in a trusted environment (e.g. the panda3d command line,
|
|
// where we've already downloaded the p3d file separately), then
|
|
// everything is approved.
|
|
mark_p3d_trusted();
|
|
return;
|
|
}
|
|
|
|
// See if we've previously approved the certificate--any certificate--that's
|
|
// signing this p3d file.
|
|
int num_signatures = _mf_reader.get_num_signatures();
|
|
for (int i = 0; i < num_signatures; ++i) {
|
|
const P3DMultifileReader::CertChain &chain = _mf_reader.get_signature(i);
|
|
|
|
// Here's a certificate that has signed this multifile.
|
|
X509 *cert = chain[0]._cert;
|
|
|
|
// Look up the certificate to see if we've stored a copy in our certs dir.
|
|
if (inst_mgr->find_cert(cert)) {
|
|
mark_p3d_trusted();
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Check the list of pre-approved certificates.
|
|
if (_certlist_package == nullptr) {
|
|
// We have to go download this package.
|
|
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
|
P3DHost *host = inst_mgr->get_host(inst_mgr->get_host_url());
|
|
_certlist_package = host->get_package("certlist", "", "", "");
|
|
if (_certlist_package != nullptr) {
|
|
_certlist_package->add_instance(this);
|
|
}
|
|
|
|
// When the package finishes downloading, we will come back here.
|
|
return;
|
|
}
|
|
|
|
if (!_certlist_package->get_ready() && !_certlist_package->get_failed()) {
|
|
// Wait for it to finish downloading.
|
|
return;
|
|
}
|
|
|
|
// Couldn't find any approved certificates.
|
|
mark_p3d_untrusted();
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* This is called internally when it has been determined that the p3d file
|
|
* can't (yet) be trusted, for instance because it lacks a signature, or
|
|
* because it is signed by an unrecognized certificate. This puts up the red
|
|
* "auth" button and waits for the user to approve the app before continuing.
|
|
*/
|
|
void P3DInstance::
|
|
mark_p3d_untrusted() {
|
|
// Failed test.
|
|
nout << "p3d untrusted\n";
|
|
if (is_failed()) {
|
|
return;
|
|
}
|
|
|
|
if (_p3dcert_package == nullptr) {
|
|
// We have to go download this package.
|
|
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
|
P3DHost *host = inst_mgr->get_host(inst_mgr->get_host_url());
|
|
_p3dcert_package = host->get_package("p3dcert", "", "", "");
|
|
if (_p3dcert_package != nullptr) {
|
|
_p3dcert_package->add_instance(this);
|
|
}
|
|
|
|
// When the package finishes downloading, we will come back here.
|
|
return;
|
|
}
|
|
|
|
if (_p3dcert_package->get_failed()) {
|
|
// Oh, too bad for us. We're dependent on this package which we weren't
|
|
// able to download for some reason.
|
|
set_failed();
|
|
}
|
|
|
|
if (!_p3dcert_package->get_ready()) {
|
|
// Wait for it to finish downloading.
|
|
return;
|
|
}
|
|
|
|
// OK, we've got the authorization program; we can put up the red button
|
|
// now.
|
|
|
|
// Notify JS that we've got no trust of the p3d file.
|
|
_main_object->set_bool_property("trusted", false);
|
|
send_notify("onunauth");
|
|
set_background_image(IT_unauth);
|
|
set_button_image(IT_auth_ready);
|
|
make_splash_window();
|
|
}
|
|
|
|
/**
|
|
* This is called internally when it has been determined that the p3d file can
|
|
* be trusted and started. When this is called, the p3d file will be examined
|
|
* and made ready to start; it will not be started until this is called.
|
|
*/
|
|
void P3DInstance::
|
|
mark_p3d_trusted() {
|
|
nout << "p3d trusted\n";
|
|
// Only call this once.
|
|
if (_p3d_trusted) {
|
|
nout << "mark_p3d_trusted() called twice on " << _fparams.get_p3d_filename()
|
|
<< "\n";
|
|
return;
|
|
}
|
|
|
|
// Extract the application desc file from the p3d file.
|
|
stringstream sstream;
|
|
if (!_mf_reader.extract_one(sstream, "p3d_info.xml")) {
|
|
nout << "No p3d_info.xml file found in " << _fparams.get_p3d_filename() << "\n";
|
|
set_failed();
|
|
|
|
} else {
|
|
sstream.seekg(0);
|
|
TiXmlDocument doc;
|
|
sstream >> doc;
|
|
|
|
scan_app_desc_file(&doc);
|
|
}
|
|
|
|
// Now we've got no further need to keep the _mf_reader open.
|
|
_mf_reader.close();
|
|
|
|
// For the moment, all sessions will be unique. TODO: support multiple
|
|
// instances per session.
|
|
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
|
ostringstream strm;
|
|
strm << inst_mgr->get_unique_id();
|
|
_session_key = strm.str();
|
|
|
|
// Notify JS that we've accepted the trust of the p3d file.
|
|
_main_object->set_bool_property("trusted", true);
|
|
send_notify("onauth");
|
|
|
|
// Now that we're all set up, grab the panda3d package. We need to examine
|
|
// this before we can start to download the remaining packages.
|
|
add_panda3d_package();
|
|
}
|
|
|
|
/**
|
|
* Reads the p3d_info.xml file at instance startup, to determine the set of
|
|
* required packages and so forth.
|
|
*/
|
|
void P3DInstance::
|
|
scan_app_desc_file(TiXmlDocument *doc) {
|
|
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
|
|
|
TiXmlElement *xpackage = doc->FirstChildElement("package");
|
|
if (xpackage == nullptr) {
|
|
return;
|
|
}
|
|
assert(_xpackage == nullptr);
|
|
_xpackage = (TiXmlElement *)xpackage->Clone();
|
|
|
|
TiXmlElement *xconfig = _xpackage->FirstChildElement("config");
|
|
if (xconfig != nullptr) {
|
|
int hidden = 0;
|
|
if (xconfig->QueryIntAttribute("hidden", &hidden) == TIXML_SUCCESS) {
|
|
if (hidden != 0) {
|
|
_hidden = true;
|
|
}
|
|
}
|
|
|
|
const char *log_basename = xconfig->Attribute("log_basename");
|
|
if (log_basename != nullptr) {
|
|
_log_basename = log_basename;
|
|
}
|
|
|
|
const char *prc_name = xconfig->Attribute("prc_name");
|
|
if (prc_name != nullptr) {
|
|
_prc_name = prc_name;
|
|
}
|
|
|
|
const char *start_dir = xconfig->Attribute("start_dir");
|
|
if (start_dir != nullptr) {
|
|
_start_dir = start_dir;
|
|
}
|
|
|
|
const char *run_origin = xconfig->Attribute("run_origin");
|
|
if (run_origin != nullptr) {
|
|
_matches_run_origin = check_matches_origin(run_origin);
|
|
}
|
|
|
|
const char *script_origin = xconfig->Attribute("script_origin");
|
|
if (script_origin != nullptr) {
|
|
_matches_script_origin = check_matches_origin(script_origin);
|
|
}
|
|
|
|
int allow_python_dev = 0;
|
|
if (xconfig->QueryIntAttribute("allow_python_dev", &allow_python_dev) == TIXML_SUCCESS) {
|
|
_allow_python_dev = (allow_python_dev != 0);
|
|
}
|
|
|
|
int keep_user_env = 0;
|
|
if (xconfig->QueryIntAttribute("keep_user_env", &keep_user_env) == TIXML_SUCCESS) {
|
|
_keep_user_env = (keep_user_env != 0);
|
|
}
|
|
|
|
int auto_start = 0;
|
|
if (xconfig->QueryIntAttribute("auto_start", &auto_start) == TIXML_SUCCESS) {
|
|
_auto_start = (auto_start != 0);
|
|
}
|
|
|
|
int auto_install = 0;
|
|
if (xconfig->QueryIntAttribute("auto_install", &auto_install) == TIXML_SUCCESS) {
|
|
_auto_install = (auto_install != 0);
|
|
}
|
|
}
|
|
|
|
nout << "_matches_run_origin = " << _matches_run_origin << "\n";
|
|
nout << "_matches_script_origin = " << _matches_script_origin << "\n";
|
|
|
|
if (inst_mgr->get_trusted_environment()) {
|
|
// If we're in a trusted environment, it is as if the origin always
|
|
// matches.
|
|
_matches_run_origin = true;
|
|
_matches_script_origin = true;
|
|
}
|
|
|
|
if (_auth_button_clicked) {
|
|
// But finally, if the user has already clicked through the red "auth"
|
|
// button, no need to present himher with another green "play" button as
|
|
// well.
|
|
_auto_install = true;
|
|
_auto_start = true;
|
|
}
|
|
|
|
nout << "_auto_install = " << _auto_install
|
|
<< ", _auto_start = " << _auto_start
|
|
<< ", _stop_on_ready = " << _stop_on_ready
|
|
<< "\n";
|
|
|
|
if (_hidden && _got_wparams) {
|
|
_wparams.set_window_type(P3D_WT_hidden);
|
|
}
|
|
|
|
if (!_matches_run_origin) {
|
|
nout << "Cannot run " << _p3d_basename << " from origin "
|
|
<< _origin_protocol << "//" << _origin_hostname
|
|
<< ":" << _origin_port << "\n";
|
|
set_failed();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds the "panda3d" package only. This package must be downloaded first,
|
|
* and its desc file examined, before we can begin downloading the other
|
|
* packages.
|
|
*/
|
|
void P3DInstance::
|
|
add_panda3d_package() {
|
|
assert(!_packages_specified);
|
|
assert(_panda3d_package == nullptr);
|
|
if (_xpackage == nullptr) {
|
|
return;
|
|
}
|
|
|
|
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
|
|
|
TiXmlElement *xrequires = _xpackage->FirstChildElement("requires");
|
|
while (xrequires != nullptr) {
|
|
const char *name = xrequires->Attribute("name");
|
|
const char *host_url = xrequires->Attribute("host");
|
|
if (name != nullptr && host_url != nullptr && strcmp(name, "panda3d") == 0) {
|
|
const char *version = xrequires->Attribute("version");
|
|
if (version == nullptr) {
|
|
version = "";
|
|
}
|
|
const char *seq = xrequires->Attribute("seq");
|
|
if (seq == nullptr) {
|
|
seq = "";
|
|
}
|
|
P3DHost *host = inst_mgr->get_host(host_url);
|
|
add_package(name, version, seq, host);
|
|
return;
|
|
}
|
|
|
|
xrequires = xrequires->NextSiblingElement("requires");
|
|
}
|
|
|
|
nout << "No panda3d package found in " << _fparams.get_p3d_filename() << "\n";
|
|
set_failed();
|
|
}
|
|
|
|
/**
|
|
* Adds the set of packages required by this p3d file to the _packages member.
|
|
* If _auto_install is true, this will also start downloading them.
|
|
*/
|
|
void P3DInstance::
|
|
add_packages() {
|
|
assert(!_packages_specified);
|
|
if (_xpackage == nullptr) {
|
|
return;
|
|
}
|
|
|
|
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
|
|
|
TiXmlElement *xrequires = _xpackage->FirstChildElement("requires");
|
|
while (xrequires != nullptr) {
|
|
const char *name = xrequires->Attribute("name");
|
|
const char *host_url = xrequires->Attribute("host");
|
|
if (name != nullptr && host_url != nullptr) {
|
|
const char *version = xrequires->Attribute("version");
|
|
if (version == nullptr) {
|
|
version = "";
|
|
}
|
|
const char *seq = xrequires->Attribute("seq");
|
|
if (seq == nullptr) {
|
|
seq = "";
|
|
}
|
|
P3DHost *host = inst_mgr->get_host(host_url);
|
|
add_package(name, version, seq, host);
|
|
}
|
|
|
|
xrequires = xrequires->NextSiblingElement("requires");
|
|
}
|
|
|
|
_packages_specified = true;
|
|
|
|
consider_start_download();
|
|
|
|
// Now that we've scanned the p3d file, and prepared the list of packages,
|
|
// it's safe to set the trusted flag.
|
|
_p3d_trusted = true;
|
|
|
|
// If the packages are already downloaded, start the instance rolling.
|
|
if (get_packages_ready()) {
|
|
mark_download_complete();
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Looks in the p3d_info.xml file for the alt_host associated with the
|
|
* indicated host_url, if any. Returns empty string if there is no match.
|
|
*/
|
|
string P3DInstance::
|
|
find_alt_host_url(const string &host_url, const string &alt_host) {
|
|
TiXmlElement *xhost = _xpackage->FirstChildElement("host");
|
|
while (xhost != nullptr) {
|
|
const char *url = xhost->Attribute("url");
|
|
if (url != nullptr && host_url == url) {
|
|
// This matches the host. Now do we have a matching alt_host keyword
|
|
// for this host?
|
|
TiXmlElement *xalt_host = xhost->FirstChildElement("alt_host");
|
|
while (xalt_host != nullptr) {
|
|
const char *keyword = xalt_host->Attribute("keyword");
|
|
if (keyword != nullptr && alt_host == keyword) {
|
|
const char *alt_host_url = xalt_host->Attribute("url");
|
|
if (alt_host_url != nullptr) {
|
|
return alt_host_url;
|
|
}
|
|
}
|
|
xalt_host = xalt_host->NextSiblingElement("alt_host");
|
|
}
|
|
}
|
|
xhost = xhost->NextSiblingElement("host");
|
|
}
|
|
|
|
return string();
|
|
}
|
|
|
|
/**
|
|
* Looks in the p3d_info.xml file for the auxiliary host information for the
|
|
* selected host. Some of this information is helpful to have before the host
|
|
* has read its own contents.xml file (particularly the host_dir
|
|
* specification).
|
|
*/
|
|
void P3DInstance::
|
|
get_host_info(P3DHost *host) {
|
|
// We should only call this function if we haven't already read the host's
|
|
// more-authoritative contents.xml file.
|
|
assert(!host->has_contents_file());
|
|
|
|
TiXmlElement *xhost = _xpackage->FirstChildElement("host");
|
|
while (xhost != nullptr) {
|
|
const char *url = xhost->Attribute("url");
|
|
if (url != nullptr && host->get_host_url() == url) {
|
|
// Found the entry for this particular host.
|
|
host->read_xhost(xhost);
|
|
return;
|
|
}
|
|
xhost = xhost->NextSiblingElement("host");
|
|
}
|
|
|
|
// Didn't find an entry for this host; oh well.
|
|
}
|
|
|
|
/**
|
|
* Determines the local path to the appropriate start directory for this
|
|
* instance, within the generic "start" directory. Returns empty string if
|
|
* this instance doesn't specify a custom start directory.
|
|
*
|
|
* If this is nonempty, it will begin with a slash--the intention is to append
|
|
* this to the end of the generic start_dir path.
|
|
*/
|
|
string P3DInstance::
|
|
get_start_dir_suffix() const {
|
|
string start_dir_suffix;
|
|
|
|
string start_dir = get_fparams().lookup_token("start_dir");
|
|
if (start_dir.empty()) {
|
|
start_dir = _start_dir;
|
|
|
|
if (!start_dir.empty()) {
|
|
// If the start_dir is taken from the p3d file (and not from the HTML
|
|
// tokens), then we also append the alt_host name to the start_dir, so
|
|
// that each alt_host variant will run in a different directory.
|
|
string alt_host = get_fparams().lookup_token("alt_host");
|
|
if (!alt_host.empty()) {
|
|
start_dir += "_";
|
|
start_dir += alt_host;
|
|
}
|
|
}
|
|
}
|
|
if (!start_dir.empty()) {
|
|
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
|
inst_mgr->append_safe_dir(start_dir_suffix, start_dir);
|
|
}
|
|
|
|
return start_dir_suffix;
|
|
}
|
|
|
|
/**
|
|
* Sends the XML sequence to inform the session of our browser's toplevel
|
|
* window object.
|
|
*/
|
|
void P3DInstance::
|
|
send_browser_script_object() {
|
|
TiXmlDocument *doc = new TiXmlDocument;
|
|
TiXmlElement *xcommand = new TiXmlElement("command");
|
|
xcommand->SetAttribute("cmd", "pyobj");
|
|
xcommand->SetAttribute("op", "set_browser_script_object");
|
|
if (_dom_object != nullptr) {
|
|
xcommand->LinkEndChild(_session->p3dobj_to_xml(_dom_object));
|
|
}
|
|
|
|
doc->LinkEndChild(xcommand);
|
|
|
|
_session->send_command(doc);
|
|
}
|
|
|
|
/**
|
|
* Creates a new P3D_request structure from the XML. Returns NULL if no
|
|
* request is needed.
|
|
*/
|
|
P3D_request *P3DInstance::
|
|
make_p3d_request(TiXmlElement *xrequest) {
|
|
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
|
P3D_request *request = nullptr;
|
|
|
|
const char *rtype = xrequest->Attribute("rtype");
|
|
if (rtype != nullptr) {
|
|
if (strcmp(rtype, "notify") == 0) {
|
|
const char *message = xrequest->Attribute("message");
|
|
if (message != nullptr) {
|
|
// A notify message from Python code.
|
|
request = new P3D_request;
|
|
request->_instance = nullptr;
|
|
request->_request_type = P3D_RT_notify;
|
|
request->_request._notify._message = strdup(message);
|
|
handle_notify_request(message);
|
|
}
|
|
|
|
} else if (strcmp(rtype, "script") == 0) {
|
|
// We don't actually build a P3D_request for a script request; we always
|
|
// just handle it immediately.
|
|
const char *operation = xrequest->Attribute("operation");
|
|
TiXmlElement *xobject = xrequest->FirstChildElement("object");
|
|
const char *property_name = xrequest->Attribute("property_name");
|
|
int needs_response = 0;
|
|
xrequest->Attribute("needs_response", &needs_response);
|
|
int unique_id = 0;
|
|
xrequest->Attribute("unique_id", &unique_id);
|
|
|
|
P3D_object *value = nullptr;
|
|
TiXmlElement *xvalue = xrequest->FirstChildElement("value");
|
|
if (xvalue != nullptr) {
|
|
value = _session->xml_to_p3dobj(xvalue);
|
|
}
|
|
if (value == nullptr) {
|
|
value = inst_mgr->new_none_object();
|
|
}
|
|
|
|
if (operation != nullptr && xobject != nullptr) {
|
|
P3D_object *object = _session->xml_to_p3dobj(xobject);
|
|
|
|
if (property_name == nullptr) {
|
|
property_name = "";
|
|
}
|
|
|
|
handle_script_request(operation, object, property_name, value,
|
|
(needs_response != 0), unique_id);
|
|
}
|
|
|
|
P3D_OBJECT_DECREF(value);
|
|
|
|
} else if (strcmp(rtype, "drop_p3dobj") == 0) {
|
|
int object_id;
|
|
if (xrequest->QueryIntAttribute("object_id", &object_id) == TIXML_SUCCESS) {
|
|
// We no longer need to keep this reference.
|
|
_session->drop_p3dobj(object_id);
|
|
}
|
|
|
|
} else if (strcmp(rtype, "stop") == 0) {
|
|
// A stop request from Python code. This is kind of weird, but OK.
|
|
request = new P3D_request;
|
|
request->_instance = nullptr;
|
|
request->_request_type = P3D_RT_stop;
|
|
|
|
} else if (strcmp(rtype, "forget_package") == 0) {
|
|
const char *host_url = xrequest->Attribute("host_url");
|
|
if (host_url != nullptr) {
|
|
// A Python-level request to remove a package from the cache.
|
|
request = new P3D_request;
|
|
request->_instance = nullptr;
|
|
request->_request_type = P3D_RT_forget_package;
|
|
request->_request._forget_package._host_url = strdup(host_url);
|
|
request->_request._forget_package._package_name = nullptr;
|
|
request->_request._forget_package._package_version = nullptr;
|
|
|
|
const char *package_name = xrequest->Attribute("package_name");
|
|
const char *package_version = xrequest->Attribute("package_version");
|
|
if (package_name != nullptr) {
|
|
request->_request._forget_package._package_name = strdup(package_name);
|
|
if (package_version != nullptr) {
|
|
request->_request._forget_package._package_version = strdup(package_version);
|
|
}
|
|
}
|
|
}
|
|
|
|
} else {
|
|
nout << "Ignoring request of type " << rtype << "\n";
|
|
}
|
|
}
|
|
|
|
if (request != nullptr) {
|
|
assert(request->_instance == nullptr);
|
|
request->_instance = this;
|
|
ref();
|
|
}
|
|
return request;
|
|
}
|
|
|
|
/**
|
|
* Called (in the main thread) when a notify request is received from the
|
|
* subprocess.
|
|
*/
|
|
void P3DInstance::
|
|
handle_notify_request(const string &message) {
|
|
// We look for certain notify events that have particular meaning to this
|
|
// instance.
|
|
if (message == "onpythonload") {
|
|
// Once Python is up and running, we can get the actual main object from
|
|
// the Python side, and merge it with our own.
|
|
|
|
TiXmlDocument *doc = new TiXmlDocument;
|
|
TiXmlElement *xcommand = new TiXmlElement("command");
|
|
xcommand->SetAttribute("cmd", "pyobj");
|
|
xcommand->SetAttribute("op", "get_panda_script_object");
|
|
doc->LinkEndChild(xcommand);
|
|
TiXmlDocument *response = _session->command_and_response(doc);
|
|
|
|
P3D_object *result = nullptr;
|
|
if (response != nullptr) {
|
|
TiXmlElement *xresponse = response->FirstChildElement("response");
|
|
if (xresponse != nullptr) {
|
|
TiXmlElement *xvalue = xresponse->FirstChildElement("value");
|
|
if (xvalue != nullptr) {
|
|
result = _session->xml_to_p3dobj(xvalue);
|
|
}
|
|
}
|
|
delete response;
|
|
}
|
|
|
|
if (result != nullptr) {
|
|
if (_matches_script_origin) {
|
|
// We only actually merge the objects if this web page is allowed to
|
|
// call our scripting functions.
|
|
_main_object->set_pyobj(result);
|
|
} else {
|
|
// Otherwise, we just do a one-time application of the toplevel
|
|
// properties down to Python.
|
|
_main_object->apply_properties(result);
|
|
}
|
|
P3D_OBJECT_DECREF(result);
|
|
}
|
|
|
|
_main_object->set_string_property("status", "starting");
|
|
|
|
} else if (message == "onwindowopen") {
|
|
// The process told us that it just successfully opened its window, for
|
|
// the first time. Hide the splash window.
|
|
_instance_window_opened = true;
|
|
if (_splash_window != nullptr) {
|
|
_splash_window->set_visible(false);
|
|
}
|
|
|
|
// Guess we won't be using (most of) these images any more.
|
|
for (int i = 0; i < (int)IT_num_image_types; ++i) {
|
|
if (i != IT_active) {
|
|
_image_files[i].cleanup();
|
|
}
|
|
}
|
|
|
|
_main_object->set_string_property("status", "open");
|
|
|
|
} else if (message == "onwindowattach") {
|
|
// The graphics window has been attached to the browser frame (maybe
|
|
// initially, maybe later). Hide the splash window.
|
|
|
|
// We don't actually hide the splash window immediately on OSX, because on
|
|
// that platform, we can hide it as soon as we render the first frame,
|
|
// avoiding an empty frame in that little period of time between the
|
|
// window opening and the first frame being drawn.
|
|
_instance_window_attached = true;
|
|
#ifndef __APPLE__
|
|
if (_splash_window != nullptr) {
|
|
_splash_window->set_visible(false);
|
|
}
|
|
#endif // __APPLE__
|
|
|
|
#ifdef __APPLE__
|
|
// Start a timer to update the frame repeatedly. This seems to be
|
|
// steadier than waiting for nullEvent.
|
|
if (_frame_timer == nullptr) {
|
|
CFRunLoopTimerContext timer_context;
|
|
memset(&timer_context, 0, sizeof(timer_context));
|
|
timer_context.info = this;
|
|
_frame_timer = CFRunLoopTimerCreate
|
|
(nullptr, 0, 1.0 / 60.0, 0, 0, timer_callback, &timer_context);
|
|
CFRunLoopRef run_loop = CFRunLoopGetCurrent();
|
|
CFRunLoopAddTimer(run_loop, _frame_timer, kCFRunLoopCommonModes);
|
|
}
|
|
#endif // __APPLE__
|
|
|
|
} else if (message == "onwindowdetach") {
|
|
// The graphics window has been removed from the browser frame. Restore
|
|
// the splash window.
|
|
_instance_window_opened = true;
|
|
_instance_window_attached = false;
|
|
set_background_image(IT_active);
|
|
if (_splash_window != nullptr) {
|
|
_splash_window->set_visible(true);
|
|
}
|
|
|
|
#ifdef __APPLE__
|
|
// Stop the frame timer; we don't need it any more.
|
|
if (_frame_timer != nullptr) {
|
|
CFRunLoopTimerInvalidate(_frame_timer);
|
|
CFRelease(_frame_timer);
|
|
_frame_timer = nullptr;
|
|
}
|
|
#endif // __APPLE__
|
|
|
|
} else if (message == "buttonclick") {
|
|
// We just got a special "button click" message from the sub-thread. This
|
|
// case is a little unusual, as it came from the splash window and not
|
|
// from Python (we presumably haven't even started Python yet). We use
|
|
// this as a sneaky way to forward the event from the sub-thread to the
|
|
// main thread.
|
|
splash_button_clicked_main_thread();
|
|
|
|
} else if (message == "authfinished") {
|
|
// Similarly for the "auth finished" message.
|
|
auth_finished_main_thread();
|
|
|
|
} else if (message == "keyboardfocus") {
|
|
if (_splash_window != nullptr) {
|
|
_splash_window->request_keyboard_focus();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called (in the main thread) when a script request is received from the
|
|
* subprocess.
|
|
*/
|
|
void P3DInstance::
|
|
handle_script_request(const string &operation, P3D_object *object,
|
|
const string &property_name, P3D_object *value,
|
|
bool needs_response, int unique_id) {
|
|
|
|
TiXmlDocument *doc = new TiXmlDocument;
|
|
TiXmlElement *xcommand = new TiXmlElement("command");
|
|
xcommand->SetAttribute("cmd", "script_response");
|
|
xcommand->SetAttribute("unique_id", unique_id);
|
|
|
|
doc->LinkEndChild(xcommand);
|
|
|
|
if (operation == "get_property") {
|
|
P3D_object *result = P3D_OBJECT_GET_PROPERTY(object, property_name.c_str());
|
|
|
|
// We've got the property value; feed it back down to the subprocess.
|
|
|
|
if (result != nullptr) {
|
|
xcommand->LinkEndChild(_session->p3dobj_to_xml(result));
|
|
P3D_OBJECT_DECREF(result);
|
|
}
|
|
|
|
} else if (operation == "set_property") {
|
|
bool result =
|
|
P3D_OBJECT_SET_PROPERTY(object, property_name.c_str(), true, value);
|
|
|
|
TiXmlElement *xvalue = new TiXmlElement("value");
|
|
xvalue->SetAttribute("type", "bool");
|
|
xvalue->SetAttribute("value", (int)result);
|
|
xcommand->LinkEndChild(xvalue);
|
|
|
|
} else if (operation == "del_property") {
|
|
bool result = P3D_OBJECT_SET_PROPERTY(object, property_name.c_str(), true, nullptr);
|
|
|
|
TiXmlElement *xvalue = new TiXmlElement("value");
|
|
xvalue->SetAttribute("type", "bool");
|
|
xvalue->SetAttribute("value", (int)result);
|
|
xcommand->LinkEndChild(xvalue);
|
|
|
|
} else if (operation == "has_method") {
|
|
bool result = P3D_OBJECT_HAS_METHOD(object, property_name.c_str());
|
|
|
|
TiXmlElement *xvalue = new TiXmlElement("value");
|
|
xvalue->SetAttribute("type", "bool");
|
|
xvalue->SetAttribute("value", (int)result);
|
|
xcommand->LinkEndChild(xvalue);
|
|
|
|
} else if (operation == "call") {
|
|
// Convert the single value parameter into an array of parameters.
|
|
P3D_object **values = &value;
|
|
int num_values = 1;
|
|
if (value->_class == &P3DObject::_object_class) {
|
|
P3DObject *p3dobj = (P3DObject *)value;
|
|
if (p3dobj->get_object_array_size() != -1) {
|
|
values = p3dobj->get_object_array();
|
|
num_values = p3dobj->get_object_array_size();
|
|
}
|
|
}
|
|
|
|
P3D_object *result =
|
|
P3D_OBJECT_CALL(object, property_name.c_str(), needs_response,
|
|
values, num_values);
|
|
|
|
if (result != nullptr) {
|
|
xcommand->LinkEndChild(_session->p3dobj_to_xml(result));
|
|
P3D_OBJECT_DECREF(result);
|
|
}
|
|
|
|
} else if (operation == "eval") {
|
|
P3D_object *result;
|
|
int size = P3D_OBJECT_GET_STRING(value, nullptr, 0);
|
|
char *buffer = new char[size + 1];
|
|
P3D_OBJECT_GET_STRING(value, buffer, size + 1);
|
|
result = P3D_OBJECT_EVAL(object, buffer);
|
|
delete[] buffer;
|
|
|
|
if (result != nullptr) {
|
|
xcommand->LinkEndChild(_session->p3dobj_to_xml(result));
|
|
P3D_OBJECT_DECREF(result);
|
|
}
|
|
}
|
|
|
|
if (needs_response) {
|
|
_session->send_command(doc);
|
|
} else {
|
|
delete doc;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the "failed" indication to display sadness to the user--we're unable
|
|
* to launch the instance for some reason.
|
|
*/
|
|
void P3DInstance::
|
|
set_failed() {
|
|
set_button_image(IT_none);
|
|
set_background_image(IT_failed);
|
|
_main_object->set_string_property("status", "failed");
|
|
|
|
if (!_failed) {
|
|
_failed = true;
|
|
make_splash_window();
|
|
send_notify("onfail");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates the splash window to be displayed at startup, if it's time.
|
|
*/
|
|
void P3DInstance::
|
|
make_splash_window() {
|
|
// Should we make the splash window visible?
|
|
bool make_visible = true;
|
|
if (_instance_window_opened) {
|
|
// Not once we've opened the main window.
|
|
make_visible = false;
|
|
}
|
|
|
|
if (_wparams.get_window_type() != P3D_WT_embedded &&
|
|
!_stuff_to_download && _auto_install &&
|
|
(_auto_start || _stop_on_ready) && _p3d_trusted) {
|
|
// If it's a toplevel or fullscreen window, then we don't want a splash
|
|
// window unless we have stuff to download, or a button to display.
|
|
make_visible = false;
|
|
}
|
|
|
|
if (is_failed()) {
|
|
// But, if we've failed to launch somehow, we need to let the user know.
|
|
make_visible = true;
|
|
}
|
|
|
|
if (_splash_window != nullptr) {
|
|
// Already got one.
|
|
_splash_window->set_visible(make_visible);
|
|
return;
|
|
}
|
|
if (!_got_wparams) {
|
|
// Don't know where to put it yet.
|
|
return;
|
|
}
|
|
if (_wparams.get_window_type() == P3D_WT_hidden && !is_failed()) {
|
|
// We're hidden, and so is the splash window. (But if we've got a failure
|
|
// case to report, we don't care and create the splash window anyway.)
|
|
return;
|
|
}
|
|
|
|
_splash_window = new SplashWindowType(this, make_visible);
|
|
|
|
// Get the splash window colors. We must set these *before* we call
|
|
// set_wparams.
|
|
if (_fparams.has_token("fgcolor")) {
|
|
int r, g, b;
|
|
if (parse_color(r, g, b, _fparams.lookup_token("fgcolor"))) {
|
|
_splash_window->set_fgcolor(r, g, b);
|
|
} else {
|
|
nout << "parse failure on fgcolor " << _fparams.lookup_token("fgcolor") << "\n";
|
|
}
|
|
}
|
|
if (_fparams.has_token("bgcolor")) {
|
|
int r, g, b;
|
|
if (parse_color(r, g, b, _fparams.lookup_token("bgcolor"))) {
|
|
_splash_window->set_bgcolor(r, g, b);
|
|
_splash_window->set_bar_bgcolor(r, g, b);
|
|
} else {
|
|
nout << "parse failure on bgcolor " << _fparams.lookup_token("bgcolor") << "\n";
|
|
}
|
|
}
|
|
if (_fparams.has_token("barcolor")) {
|
|
int r, g, b;
|
|
if (parse_color(r, g, b, _fparams.lookup_token("barcolor"))) {
|
|
_splash_window->set_barcolor(r, g, b);
|
|
} else {
|
|
nout << "parse failure on barcolor " << _fparams.lookup_token("barcolor") << "\n";
|
|
}
|
|
}
|
|
if (_fparams.has_token("bar_bgcolor")) {
|
|
int r, g, b;
|
|
if (parse_color(r, g, b, _fparams.lookup_token("bar_bgcolor"))) {
|
|
_splash_window->set_bar_bgcolor(r, g, b);
|
|
} else {
|
|
nout << "parse failure on bar_bgcolor " << _fparams.lookup_token("bar_bgcolor") << "\n";
|
|
}
|
|
}
|
|
if (_fparams.has_token("bar_border")) {
|
|
int border = _fparams.lookup_token_int("bar_border");
|
|
_splash_window->set_bar_border(border);
|
|
}
|
|
if (_fparams.has_token("bar_bottom")) {
|
|
int bottom = _fparams.lookup_token_int("bar_bottom");
|
|
_splash_window->set_bar_bottom(bottom);
|
|
}
|
|
if (_fparams.has_token("bar_width")) {
|
|
string bar_width = _fparams.lookup_token("bar_width");
|
|
char *endptr;
|
|
long width = strtol(bar_width.c_str(), &endptr, 10);
|
|
bool percent = false;
|
|
|
|
if (*endptr == '%') {
|
|
percent = true;
|
|
++endptr;
|
|
}
|
|
if (*endptr == '\0' && width >= 0 && width < 65536) {
|
|
_splash_window->set_bar_width((int)width, percent);
|
|
} else {
|
|
nout << "parse failure on bar_width " << bar_width << "\n";
|
|
}
|
|
}
|
|
if (_fparams.has_token("bar_height")) {
|
|
string bar_height = _fparams.lookup_token("bar_height");
|
|
char *endptr;
|
|
long height = strtol(bar_height.c_str(), &endptr, 10);
|
|
bool percent = false;
|
|
|
|
if (*endptr == '%') {
|
|
percent = true;
|
|
++endptr;
|
|
}
|
|
if (*endptr == '\0' && height >= 0 && height < 65536) {
|
|
_splash_window->set_bar_height((int)height, percent);
|
|
} else {
|
|
nout << "parse failure on bar_height " << bar_height << "\n";
|
|
}
|
|
}
|
|
if (_fparams.has_token("font_family")) {
|
|
string family = _fparams.lookup_token("font_family");
|
|
_splash_window->set_font_family(family);
|
|
}
|
|
if (_fparams.has_token("font_size")) {
|
|
int size = _fparams.lookup_token_int("font_size");
|
|
_splash_window->set_font_size(size);
|
|
}
|
|
if (_fparams.has_token("font_style")) {
|
|
string style = _fparams.lookup_token("font_style");
|
|
|
|
if (style == "normal") {
|
|
_splash_window->set_font_style(P3DSplashWindow::FS_normal);
|
|
} else if (style == "oblique") {
|
|
_splash_window->set_font_style(P3DSplashWindow::FS_oblique);
|
|
} else if (style == "italic") {
|
|
_splash_window->set_font_style(P3DSplashWindow::FS_italic);
|
|
} else {
|
|
nout << "parse_failure on font_style " << style << "\n";
|
|
}
|
|
}
|
|
if (_fparams.has_token("font_weight")) {
|
|
string weight = _fparams.lookup_token("font_weight");
|
|
|
|
if (weight == "normal") {
|
|
_splash_window->set_font_weight(400);
|
|
} else if (weight == "bold") {
|
|
_splash_window->set_font_weight(700);
|
|
} else if (weight == "bolder") {
|
|
_splash_window->set_font_weight(700);
|
|
} else if (weight == "lighter") {
|
|
_splash_window->set_font_weight(100);
|
|
} else if (weight.size() == 3 &&
|
|
weight[0] >= '1' && weight[0] <= '9' &&
|
|
weight[1] == '0' && weight[2] == '0') {
|
|
_splash_window->set_font_weight(((int)weight[0] - 48) * 100);
|
|
} else {
|
|
nout << "parse_failure on font_weight " << weight << "\n";
|
|
}
|
|
}
|
|
|
|
_splash_window->set_wparams(_wparams);
|
|
_splash_window->set_install_label(_install_label);
|
|
|
|
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
|
|
|
// Go get the required images.
|
|
for (int i = 0; i < (int)IT_none; ++i) {
|
|
string token_keyword = string(_image_type_names[i]) + "_img";
|
|
if (!_fparams.has_token(token_keyword) && i < (int)IT_auth_ready) {
|
|
token_keyword = "splash_img";
|
|
}
|
|
|
|
string image_url = _fparams.lookup_token(token_keyword);
|
|
if (image_url.empty()) {
|
|
// No specific image for this type is specified; get the default image.
|
|
// We do this via the P3DPackage interface, so we can use the cached
|
|
// version on disk if it's good.
|
|
_image_files[i]._use_standard_image = true;
|
|
|
|
} else {
|
|
// We have an explicit image specified for this slot, so just download
|
|
// it directly. This one won't be cached locally (though the browser
|
|
// might be free to cache it).
|
|
_image_files[i]._use_standard_image = false;
|
|
_image_files[i]._filename.clear();
|
|
|
|
// Make a temporary file to receive the splash image.
|
|
assert(_image_files[i]._temp_filename == nullptr);
|
|
_image_files[i]._temp_filename = new P3DTemporaryFile(".jpg");
|
|
|
|
// Start downloading the requested image.
|
|
ImageDownload *download = new ImageDownload(this, i);
|
|
download->set_url(image_url);
|
|
download->set_filename(_image_files[i]._temp_filename->get_filename());
|
|
|
|
start_download(download);
|
|
}
|
|
}
|
|
|
|
if (_current_background_image != IT_none) {
|
|
_splash_window->set_image_filename(_image_files[_current_background_image]._filename, P3DSplashWindow::IP_background);
|
|
}
|
|
|
|
if (_current_button_image != IT_none) {
|
|
_splash_window->set_image_filename(_image_files[_current_button_image]._filename, P3DSplashWindow::IP_button_ready);
|
|
_splash_window->set_image_filename(_image_files[_current_button_image + 1]._filename, P3DSplashWindow::IP_button_rollover);
|
|
_splash_window->set_image_filename(_image_files[_current_button_image + 2]._filename, P3DSplashWindow::IP_button_click);
|
|
_splash_window->set_button_active(true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Specifies the particular image that should be displayed as the background
|
|
* image in the splash window. Specify IT_none to take the background image
|
|
* away.
|
|
*/
|
|
void P3DInstance::
|
|
set_background_image(ImageType image_type) {
|
|
if (is_failed()) {
|
|
// Can't change the background again after we've failed.
|
|
return;
|
|
}
|
|
|
|
if (image_type != _current_background_image) {
|
|
nout << "setting background to " << _image_type_names[image_type]
|
|
<< ", splash_window = " << _splash_window << "\n";
|
|
// Remove the previous image.
|
|
_image_files[_current_background_image]._image_placement = P3DSplashWindow::IP_none;
|
|
|
|
// Install the new image.
|
|
_current_background_image = image_type;
|
|
if (_current_background_image != IT_none) {
|
|
_image_files[_current_background_image]._image_placement = P3DSplashWindow::IP_background;
|
|
}
|
|
|
|
// Update the splash window.
|
|
if (_splash_window != nullptr) {
|
|
_splash_window->set_image_filename(_image_files[_current_background_image]._filename, P3DSplashWindow::IP_background);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Specifies the particular image that should be displayed as the button image
|
|
* in the splash window. Specify IT_none to take the button image away.
|
|
*
|
|
* This actually defines a trilogy of button images: ready, rollover, click.
|
|
*/
|
|
void P3DInstance::
|
|
set_button_image(ImageType image_type) {
|
|
if (is_failed()) {
|
|
// Can't set the button again after we've failed.
|
|
return;
|
|
}
|
|
|
|
if (image_type != _current_button_image) {
|
|
nout << "setting button to " << _image_type_names[image_type] << "\n";
|
|
// Remove the previous image.
|
|
_image_files[_current_button_image]._image_placement = P3DSplashWindow::IP_none;
|
|
if (_current_button_image != IT_none) {
|
|
_image_files[_current_button_image + 1]._image_placement = P3DSplashWindow::IP_none;
|
|
_image_files[_current_button_image + 2]._image_placement = P3DSplashWindow::IP_none;
|
|
}
|
|
|
|
// Install the new image.
|
|
_current_button_image = image_type;
|
|
if (_current_button_image != IT_none) {
|
|
_image_files[_current_button_image]._image_placement = P3DSplashWindow::IP_button_ready;
|
|
_image_files[_current_button_image + 1]._image_placement = P3DSplashWindow::IP_button_rollover;
|
|
_image_files[_current_button_image + 2]._image_placement = P3DSplashWindow::IP_button_click;
|
|
}
|
|
|
|
// Update the splash window.
|
|
if (_splash_window != nullptr) {
|
|
if (_current_button_image != IT_none) {
|
|
_splash_window->set_image_filename(_image_files[_current_button_image]._filename, P3DSplashWindow::IP_button_ready);
|
|
_splash_window->set_image_filename(_image_files[_current_button_image + 1]._filename, P3DSplashWindow::IP_button_rollover);
|
|
_splash_window->set_image_filename(_image_files[_current_button_image + 2]._filename, P3DSplashWindow::IP_button_click);
|
|
_splash_window->set_button_active(true);
|
|
} else {
|
|
_splash_window->set_button_active(false);
|
|
}
|
|
}
|
|
|
|
} else {
|
|
// We're not changing the button graphic, but we might be re-activating
|
|
// it.
|
|
if (_splash_window != nullptr) {
|
|
if (_current_button_image != IT_none) {
|
|
_splash_window->set_button_active(true);
|
|
} else {
|
|
_splash_window->set_button_active(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Notified when a package information has been successfully downloaded and
|
|
* the package is idle, waiting for activate_download() to be called.
|
|
*/
|
|
void P3DInstance::
|
|
report_package_info_ready(P3DPackage *package) {
|
|
nout << "report_package_info_ready: " << package->get_package_name() << "\n";
|
|
if (package == _image_package || package == _certlist_package ||
|
|
package == _p3dcert_package) {
|
|
// A special case: these packages get immediately downloaded, without
|
|
// waiting for anything else.
|
|
if (package == _certlist_package || package == _p3dcert_package) {
|
|
// If we're downloading one of the two cert packages, though, put up a
|
|
// progress bar.
|
|
make_splash_window();
|
|
if (_splash_window != nullptr) {
|
|
_splash_window->set_install_progress(0.0, true, 0);
|
|
}
|
|
if (package == _certlist_package) {
|
|
set_install_label("Getting Certificates");
|
|
} else {
|
|
set_install_label("Getting Authorization Dialog");
|
|
}
|
|
}
|
|
|
|
package->activate_download();
|
|
return;
|
|
}
|
|
|
|
if (package == _panda3d_package && !_packages_specified) {
|
|
// Another special case. Once the special panda3d package is ready to
|
|
// download (and we know what platform it belongs to), we can begin to
|
|
// download the remaining required packages.
|
|
string package_platform = package->get_package_platform();
|
|
if (!package_platform.empty() && _session_platform.empty()) {
|
|
// From now on, all platform-specific files downloaded by this session
|
|
// will be for this platform.
|
|
_session_platform = package_platform;
|
|
}
|
|
if (_session_platform != package_platform) {
|
|
nout << "Error: session is " << _session_platform
|
|
<< ", but we somehow got panda3d for " << package_platform << "\n";
|
|
set_failed();
|
|
return;
|
|
}
|
|
|
|
add_packages();
|
|
}
|
|
|
|
// Now that the package's info is ready, we know its set of required
|
|
// packages, and we can add these to the list.
|
|
P3DPackage::Requires::const_iterator ri;
|
|
for (ri = package->_requires.begin(); ri != package->_requires.end(); ++ri) {
|
|
const P3DPackage::RequiredPackage &rp = (*ri);
|
|
add_package(rp._package_name, rp._package_version, rp._package_seq,
|
|
rp._host);
|
|
}
|
|
|
|
consider_start_download();
|
|
}
|
|
|
|
/**
|
|
* When all package info files have been obtained, begins downloading stuff.
|
|
*/
|
|
void P3DInstance::
|
|
consider_start_download() {
|
|
if (get_packages_info_ready()) {
|
|
// All packages are ready to go. Let's start some download action.
|
|
_downloading_packages.clear();
|
|
_prev_downloaded = 0;
|
|
_total_download_size = 0;
|
|
Packages::const_iterator pi;
|
|
for (pi = _packages.begin(); pi != _packages.end(); ++pi) {
|
|
P3DPackage *package = (*pi);
|
|
if (package->get_info_ready()) {
|
|
if (!package->get_ready()) {
|
|
_downloading_packages.push_back(package);
|
|
_total_download_size += package->get_download_size();
|
|
} else {
|
|
_prev_downloaded += package->get_download_size();
|
|
}
|
|
}
|
|
}
|
|
ready_to_install();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called when it's time to start the package download process.
|
|
*/
|
|
void P3DInstance::
|
|
ready_to_install() {
|
|
if (_downloading_packages.empty() && _download_complete) {
|
|
// We have already been here. Ignore it.
|
|
|
|
} else if (!_auto_install && !_download_started) {
|
|
// Not authorized to download yet. We're waiting for the user to
|
|
// acknowledge the download.
|
|
set_background_image(IT_ready);
|
|
set_button_image(IT_play_ready);
|
|
|
|
} else {
|
|
_download_started = true;
|
|
_download_complete = false;
|
|
_download_package_index = 0;
|
|
_total_downloaded = 0;
|
|
|
|
// Record the time we started the package download, so we can report
|
|
// downloadElapsedTime and predict downloadRemainingTime.
|
|
#ifdef _WIN32
|
|
_start_dl_tick = GetTickCount();
|
|
#else
|
|
gettimeofday(&_start_dl_timeval, nullptr);
|
|
#endif
|
|
|
|
nout << "Beginning install of " << _downloading_packages.size()
|
|
<< " packages, total " << _total_download_size
|
|
<< " bytes required (" << _prev_downloaded
|
|
<< " previously downloaded).\n";
|
|
|
|
if (_downloading_packages.size() > 0) {
|
|
_stuff_to_download = true;
|
|
|
|
// Maybe it's time to open a splash window now.
|
|
make_splash_window();
|
|
}
|
|
|
|
_main_object->set_string_property("status", "downloading");
|
|
_main_object->set_int_property("numDownloadingPackages", _downloading_packages.size());
|
|
_main_object->set_int_property("totalDownloadSize", _total_download_size);
|
|
_main_object->set_int_property("downloadElapsedSeconds", 0);
|
|
_main_object->set_undefined_property("downloadRemainingSeconds");
|
|
|
|
double progress = 0.0;
|
|
if (_prev_downloaded != 0) {
|
|
// We might start off with more than 0 progress, if we've already
|
|
// downloaded some of it previously.
|
|
progress = (_prev_downloaded) / (_total_download_size + _prev_downloaded);
|
|
progress = min(progress, 1.0);
|
|
|
|
_main_object->set_float_property("downloadProgress", progress);
|
|
}
|
|
if (_splash_window != nullptr) {
|
|
_splash_window->set_install_progress(progress, true, 0);
|
|
}
|
|
|
|
send_notify("ondownloadbegin");
|
|
|
|
start_next_download();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks whether all packages are ready and waiting to be downloaded; if so,
|
|
* starts the next package in sequence downloading.
|
|
*/
|
|
void P3DInstance::
|
|
start_next_download() {
|
|
while (_download_package_index < (int)_downloading_packages.size()) {
|
|
P3DPackage *package = _downloading_packages[_download_package_index];
|
|
if (package->get_failed()) {
|
|
send_notify("ondownloadfail");
|
|
set_failed();
|
|
return;
|
|
}
|
|
|
|
if (!package->get_ready()) {
|
|
// This package is ready to download. Begin.
|
|
string name = package->get_formatted_name();
|
|
_main_object->set_string_property("downloadPackageName", package->get_package_name());
|
|
_main_object->set_string_property("downloadPackageDisplayName", name);
|
|
_main_object->set_int_property("downloadPackageNumber", _download_package_index + 1);
|
|
_main_object->set_int_property("downloadPackageSize", package->get_download_size());
|
|
set_install_label("Installing " + name);
|
|
|
|
nout << "Installing " << package->get_package_name()
|
|
<< ", package " << _download_package_index + 1
|
|
<< " of " << _downloading_packages.size()
|
|
<< ", " << package->get_download_size()
|
|
<< " bytes.\n";
|
|
|
|
package->activate_download();
|
|
send_notify("ondownloadnext");
|
|
return;
|
|
}
|
|
|
|
// This package has been downloaded. Move to the next.
|
|
_total_downloaded += package->get_download_size();
|
|
++_download_package_index;
|
|
}
|
|
|
|
// Looks like we're all done downloading. Launch!
|
|
_downloading_packages.clear();
|
|
|
|
if (get_packages_ready()) {
|
|
mark_download_complete();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called internally when all files needed to launch have been downloaded.
|
|
*/
|
|
void P3DInstance::
|
|
mark_download_complete() {
|
|
if (_failed) {
|
|
return;
|
|
}
|
|
|
|
if (!_download_complete) {
|
|
_download_complete = true;
|
|
_main_object->set_bool_property("downloadComplete", true);
|
|
_main_object->set_string_property("status", "downloadcomplete");
|
|
send_notify("ondownloadcomplete");
|
|
}
|
|
|
|
// Take down the download progress bar.
|
|
if (_splash_window != nullptr) {
|
|
_splash_window->set_install_progress(0.0, true, 0);
|
|
}
|
|
set_install_label("");
|
|
|
|
if (_got_wparams && _p3d_trusted) {
|
|
ready_to_start();
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Called internally when we have got the wparams and fparams and we have
|
|
* downloaded all required packages.
|
|
*/
|
|
void P3DInstance::
|
|
ready_to_start() {
|
|
if (_instance_started || is_failed()) {
|
|
// Already started--or never mind.
|
|
return;
|
|
}
|
|
|
|
_main_object->set_string_property("status", "ready");
|
|
send_notify("onready");
|
|
|
|
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
|
if (_stop_on_ready) {
|
|
// If we've got the "stop_on_ready" token, then exit abruptly now, instead
|
|
// of displaying the splash window.
|
|
request_stop_main_thread();
|
|
return;
|
|
}
|
|
|
|
if (_auto_start) {
|
|
set_background_image(IT_launch);
|
|
inst_mgr->start_instance(this);
|
|
|
|
} else {
|
|
// We're fully downloaded, and waiting for the user to click play.
|
|
set_background_image(IT_ready);
|
|
set_button_image(IT_play_ready);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Notified as the instance file is downloaded.
|
|
*/
|
|
void P3DInstance::
|
|
report_instance_progress(double progress, bool is_progress_known,
|
|
size_t received_data) {
|
|
if (!_show_dl_instance_progress) {
|
|
// If we haven't yet set the download label, set it after a full second
|
|
// has elapsed. We don't want to set it too soon, because we're not
|
|
// really sure how long it will take to download (the instance file might
|
|
// be already in the browser cache).
|
|
#ifdef _WIN32
|
|
int now = GetTickCount();
|
|
double elapsed = (double)(now - _start_dl_tick) * 0.001;
|
|
#else
|
|
struct timeval now;
|
|
gettimeofday(&now, nullptr);
|
|
double elapsed = (double)(now.tv_sec - _start_dl_timeval.tv_sec) +
|
|
(double)(now.tv_usec - _start_dl_timeval.tv_usec) / 1000000.0;
|
|
#endif
|
|
|
|
// Put up the progress bar after 2 seconds have elapsed, if we've still
|
|
// got some distance to go; or after 5 seconds have elapsed regardless.
|
|
if ((elapsed > 2.0 && progress < 0.7) ||
|
|
(elapsed > 5.0)) {
|
|
_show_dl_instance_progress = true;
|
|
if (_fparams.has_token("p3d_install_label")) {
|
|
set_install_label(_fparams.lookup_token("p3d_install_label"));
|
|
} else {
|
|
set_install_label("Getting " + _p3d_basename);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (_splash_window != nullptr && _show_dl_instance_progress) {
|
|
_splash_window->set_install_progress(progress, is_progress_known, received_data);
|
|
}
|
|
_main_object->set_float_property("instanceDownloadProgress", progress);
|
|
}
|
|
|
|
/**
|
|
* Notified as the packages required by the instance file are downloaded.
|
|
*/
|
|
void P3DInstance::
|
|
report_package_progress(P3DPackage *package, double progress) {
|
|
if (package == _image_package) {
|
|
// Ignore this.
|
|
return;
|
|
}
|
|
if (package == _certlist_package || package == _p3dcert_package) {
|
|
// This gets its own progress bar.
|
|
if (_splash_window != nullptr) {
|
|
_splash_window->set_install_progress(progress, true, 0);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (_download_package_index >= (int)_downloading_packages.size() ||
|
|
package != _downloading_packages[_download_package_index]) {
|
|
// Quietly ignore a download progress report from an unexpected package.
|
|
return;
|
|
}
|
|
|
|
// Scale the progress into the range appropriate to this package.
|
|
progress = (progress * package->get_download_size() + _total_downloaded + _prev_downloaded) / (_total_download_size + _prev_downloaded);
|
|
progress = min(progress, 1.0);
|
|
|
|
if (_splash_window != nullptr) {
|
|
_splash_window->set_install_progress(progress, true, 0);
|
|
}
|
|
_main_object->set_float_property("downloadProgress", progress);
|
|
|
|
static const size_t buffer_size = 256;
|
|
char buffer[buffer_size];
|
|
|
|
// Get the floating-point elapsed time.
|
|
#ifdef _WIN32
|
|
int now = GetTickCount();
|
|
double elapsed = (double)(now - _start_dl_tick) * 0.001;
|
|
#else
|
|
struct timeval now;
|
|
gettimeofday(&now, nullptr);
|
|
double elapsed = (double)(now.tv_sec - _start_dl_timeval.tv_sec) +
|
|
(double)(now.tv_usec - _start_dl_timeval.tv_usec) / 1000000.0;
|
|
#endif
|
|
|
|
int ielapsed = (int)elapsed;
|
|
_main_object->set_int_property("downloadElapsedSeconds", ielapsed);
|
|
|
|
sprintf(buffer, "%d:%02d", ielapsed / 60, ielapsed % 60);
|
|
_main_object->set_string_property("downloadElapsedFormatted", buffer);
|
|
|
|
if (progress > 0 && (elapsed > 5.0 || progress > 0.2)) {
|
|
double this_total = elapsed / progress;
|
|
double this_remaining = max(this_total - elapsed, 0.0);
|
|
|
|
// Age out any old time reports.
|
|
double old = elapsed - min(time_average, this_remaining);
|
|
while (!_time_reports.empty() && _time_reports.front()._report_time < old) {
|
|
TimeReport &tr0 = _time_reports.front();
|
|
_total_time_reports -= tr0._total;
|
|
_time_reports.pop_front();
|
|
}
|
|
if (_time_reports.empty()) {
|
|
_total_time_reports = 0.0;
|
|
}
|
|
|
|
// Add a new time report.
|
|
TimeReport tr;
|
|
tr._total = this_total;
|
|
tr._report_time = elapsed;
|
|
_time_reports.push_back(tr);
|
|
_total_time_reports += tr._total;
|
|
|
|
// Now get the average report.
|
|
if (!_time_reports.empty()) {
|
|
double total = _total_time_reports / (double)_time_reports.size();
|
|
double remaining = max(total - elapsed, 0.0);
|
|
int iremaining = (int)(remaining + 0.5);
|
|
_main_object->set_int_property("downloadRemainingSeconds", iremaining);
|
|
sprintf(buffer, "%d:%02d", iremaining / 60, iremaining % 60);
|
|
_main_object->set_string_property("downloadRemainingFormatted", buffer);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Notified when a required package is fully downloaded, or failed.
|
|
*/
|
|
void P3DInstance::
|
|
report_package_done(P3DPackage *package, bool success) {
|
|
nout << "Done installing " << package->get_package_name()
|
|
<< ": success = " << success << "\n";
|
|
|
|
if (package == _image_package) {
|
|
// A special case: we just downloaded the image package, so get the image
|
|
// files out of it and point them to the splash window.
|
|
string package_dir = package->get_package_dir();
|
|
const TiXmlElement *xconfig = package->get_xconfig();
|
|
if (xconfig == nullptr) {
|
|
nout << "No <config> entry in image package\n";
|
|
return;
|
|
}
|
|
package->mark_used();
|
|
|
|
for (int i = 0; i < (int)IT_none; ++i) {
|
|
if (_image_files[i]._use_standard_image) {
|
|
// This image indexes into the package. Go get the standard image
|
|
// filename.
|
|
string token = string(_image_type_names[i]) + "_img";
|
|
const string *basename = xconfig->Attribute(token);
|
|
if (basename == nullptr) {
|
|
nout << "No entry in image package for " << token << "\n";
|
|
} else {
|
|
string image_filename = package_dir + "/" + *basename;
|
|
_image_files[i]._filename = image_filename;
|
|
|
|
// If the image should be on the window now, and the window still
|
|
// exists, put it up.
|
|
if (_splash_window != nullptr &&
|
|
_image_files[i]._image_placement != P3DSplashWindow::IP_none) {
|
|
P3DSplashWindow::ImagePlacement image_placement = _image_files[i]._image_placement;
|
|
_splash_window->set_image_filename(image_filename, image_placement);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (package == _certlist_package) {
|
|
// Another special case: successfully downloading certlist (or failing to
|
|
// download it) means we can finish checking the authenticity of the p3d
|
|
// file.
|
|
|
|
package->mark_used();
|
|
|
|
// Take down the download progress.
|
|
if (_splash_window != nullptr) {
|
|
_splash_window->set_install_progress(0.0, true, 0);
|
|
}
|
|
set_install_label("");
|
|
|
|
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
|
inst_mgr->read_certlist(package);
|
|
check_p3d_signature();
|
|
return;
|
|
}
|
|
|
|
if (package == _p3dcert_package) {
|
|
// Another special case: successfully downloading p3dcert means we can
|
|
// enable the auth button.
|
|
|
|
package->mark_used();
|
|
|
|
// Take down the download progress.
|
|
if (_splash_window != nullptr) {
|
|
_splash_window->set_install_progress(0.0, true, 0);
|
|
}
|
|
set_install_label("");
|
|
mark_p3d_untrusted();
|
|
return;
|
|
}
|
|
|
|
if (success) {
|
|
report_package_progress(package, 1.0);
|
|
start_next_download();
|
|
} else {
|
|
send_notify("ondownloadfail");
|
|
set_failed();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the install label that will be displayed on the splash window, if it
|
|
* is present.
|
|
*/
|
|
void P3DInstance::
|
|
set_install_label(const string &install_label) {
|
|
_install_label = install_label;
|
|
if (_splash_window != nullptr) {
|
|
_splash_window->set_install_label(_install_label);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Actually paints the rendered image to the browser window. This is only
|
|
* needed for OSX, where the child process isn't allowed to do it directly.
|
|
*/
|
|
void P3DInstance::
|
|
paint_window() {
|
|
#ifdef __APPLE__
|
|
const P3D_window_handle &handle = _wparams.get_parent_window();
|
|
if (handle._window_handle_type == P3D_WHT_osx_port) {
|
|
#if !__LP64__
|
|
paint_window_osx_port();
|
|
#endif
|
|
|
|
} else if (handle._window_handle_type == P3D_WHT_osx_cgcontext) {
|
|
const P3D_window_handle &handle = _wparams.get_parent_window();
|
|
assert(handle._window_handle_type == P3D_WHT_osx_cgcontext);
|
|
CGContextRef context = handle._handle._osx_cgcontext._context;
|
|
|
|
paint_window_osx_cgcontext(context);
|
|
}
|
|
#endif // __APPLE__
|
|
}
|
|
|
|
#if defined(__APPLE__) && !__LP64__
|
|
/**
|
|
* Fills _reversed_buffer with the pixels from the current frame, suitable for
|
|
* rendering via the old QuickDraw interface. Returns true on success, or
|
|
* false if there is no Panda3D window visible. Only needed on OSX.
|
|
*/
|
|
bool P3DInstance::
|
|
get_framebuffer_osx_port() {
|
|
if (_swbuffer == nullptr || !_instance_window_attached) {
|
|
// We don't have a Panda3D window yet.
|
|
return false;
|
|
}
|
|
|
|
// blit rendered framebuffer into window backing store
|
|
int x_size = min(_wparams.get_win_width(), _swbuffer->get_x_size());
|
|
int y_size = min(_wparams.get_win_height(), _swbuffer->get_y_size());
|
|
size_t rowsize = _swbuffer->get_row_size();
|
|
|
|
if (_swbuffer->ready_for_read()) {
|
|
// Copy the new framebuffer image from the child process.
|
|
const void *framebuffer = _swbuffer->open_read_framebuffer();
|
|
|
|
// We have to reverse the image vertically first (different conventions
|
|
// between Panda and Mac).
|
|
for (int yi = 0; yi < y_size; ++yi) {
|
|
#ifndef __BIG_ENDIAN__
|
|
// On a little-endian machine, we only have to reverse the order of the
|
|
// rows.
|
|
memcpy(_reversed_buffer + (y_size - 1 - yi) * rowsize,
|
|
(char *)framebuffer + yi * rowsize,
|
|
rowsize);
|
|
|
|
#else // __BIG_ENDIAN__
|
|
// On a big-endian machine, we need to do more work.
|
|
|
|
// It appears that kBGRAPixelFormat, below, is ignored on big-endian
|
|
// machines, and it is treated as KARGBPixelFormat regardless of what we
|
|
// specify. Vexing. To compensate for this, we have to reverse the
|
|
// color channels ourselves on big-endian machines.
|
|
|
|
const char *source = (const char *)framebuffer + yi * rowsize;
|
|
const char *stop = source + x_size * 4;
|
|
char *dest = (_reversed_buffer + (y_size - 1 - yi) * rowsize);
|
|
while (source < stop) {
|
|
char b = source[0];
|
|
char g = source[1];
|
|
char r = source[2];
|
|
char a = source[3];
|
|
dest[0] = a;
|
|
dest[1] = r;
|
|
dest[2] = g;
|
|
dest[3] = b;
|
|
dest += 4;
|
|
source += 4;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
_swbuffer->close_read_framebuffer();
|
|
|
|
if (_splash_window != nullptr && _splash_window->get_visible()) {
|
|
// If the splash window is up, time to hide it. We've just rendered a
|
|
// real frame.
|
|
_splash_window->set_visible(false);
|
|
}
|
|
|
|
} else {
|
|
// No frame ready. Just re-paint the frame we had saved last time.
|
|
}
|
|
|
|
return true;
|
|
}
|
|
#endif // __APPLE__
|
|
|
|
#ifdef __APPLE__
|
|
/**
|
|
* Fills _reversed_buffer with the pixels from the current frame, suitable for
|
|
* rendering via the new CoreGraphics interface. Returns true on success, or
|
|
* false if there is no Panda3D window visible. Only needed on OSX.
|
|
*/
|
|
bool P3DInstance::
|
|
get_framebuffer_osx_cgcontext() {
|
|
if (_swbuffer == nullptr || !_instance_window_attached) {
|
|
// We don't have a Panda3D window yet.
|
|
return false;
|
|
}
|
|
|
|
// blit rendered framebuffer into window backing store
|
|
int x_size = min(_wparams.get_win_width(), _swbuffer->get_x_size());
|
|
int y_size = min(_wparams.get_win_height(), _swbuffer->get_y_size());
|
|
size_t rowsize = _swbuffer->get_row_size();
|
|
|
|
if (_swbuffer->ready_for_read()) {
|
|
// Copy the new framebuffer image from the child process.
|
|
const void *framebuffer = _swbuffer->open_read_framebuffer();
|
|
memcpy(_reversed_buffer, framebuffer, y_size * rowsize);
|
|
_swbuffer->close_read_framebuffer();
|
|
|
|
if (_splash_window != nullptr && _splash_window->get_visible()) {
|
|
// If the splash window is up, time to hide it. We've just rendered a
|
|
// real frame.
|
|
_splash_window->set_visible(false);
|
|
}
|
|
|
|
} else {
|
|
// No frame ready. Just re-paint the frame we had saved last time.
|
|
}
|
|
|
|
return true;
|
|
}
|
|
#endif // __APPLE__
|
|
|
|
#if defined(__APPLE__) && !__LP64__
|
|
/**
|
|
* Actually paints the rendered image to the browser window, using the OSX
|
|
* deprecated QuickDraw interfaces.
|
|
*/
|
|
void P3DInstance::
|
|
paint_window_osx_port() {
|
|
if (!get_framebuffer_osx_port()) {
|
|
// No Panda3D window is showing.
|
|
return;
|
|
}
|
|
|
|
int x_size = min(_wparams.get_win_width(), _swbuffer->get_x_size());
|
|
int y_size = min(_wparams.get_win_height(), _swbuffer->get_y_size());
|
|
size_t rowsize = _swbuffer->get_row_size();
|
|
|
|
Rect src_rect = {0, 0, (short)y_size, (short)x_size};
|
|
Rect ddrc_rect = {0, 0, (short)y_size, (short)x_size};
|
|
|
|
QDErr err;
|
|
|
|
GWorldPtr pGWorld;
|
|
err = NewGWorldFromPtr(&pGWorld, k32BGRAPixelFormat, &src_rect, 0, 0, 0,
|
|
_reversed_buffer, rowsize);
|
|
if (err != noErr) {
|
|
nout << " error in NewGWorldFromPtr, called from paint_window()\n";
|
|
return;
|
|
}
|
|
|
|
const P3D_window_handle &handle = _wparams.get_parent_window();
|
|
assert(handle._window_handle_type == P3D_WHT_osx_port);
|
|
GrafPtr out_port = handle._handle._osx_port._port;
|
|
GrafPtr port_save = nullptr;
|
|
Boolean port_changed = QDSwapPort(out_port, &port_save);
|
|
|
|
// Make sure the clipping rectangle isn't in the way. Is there a better way
|
|
// to eliminate the cliprect from consideration?
|
|
Rect r = { 0, 0, 0x7fff, 0x7fff };
|
|
ClipRect(&r);
|
|
|
|
CopyBits(GetPortBitMapForCopyBits(pGWorld),
|
|
GetPortBitMapForCopyBits(out_port),
|
|
&src_rect, &ddrc_rect, srcCopy, 0);
|
|
|
|
if (port_changed) {
|
|
QDSwapPort(port_save, nullptr);
|
|
}
|
|
|
|
DisposeGWorld(pGWorld);
|
|
}
|
|
#endif // __APPLE__
|
|
|
|
#ifdef __APPLE__
|
|
/**
|
|
* Actually paints the rendered image to the browser window. This is the
|
|
* newer CoreGraphics implementation on OSX.
|
|
*/
|
|
void P3DInstance::
|
|
paint_window_osx_cgcontext(CGContextRef context) {
|
|
if (!get_framebuffer_osx_cgcontext()) {
|
|
// No Panda3D window is showing.
|
|
return;
|
|
}
|
|
|
|
int x_size = min(_wparams.get_win_width(), _swbuffer->get_x_size());
|
|
int y_size = min(_wparams.get_win_height(), _swbuffer->get_y_size());
|
|
|
|
if (_buffer_image != nullptr) {
|
|
CGRect region = { { 0, 0 }, { (CGFloat)x_size, (CGFloat)y_size } };
|
|
CGContextDrawImage(context, region, _buffer_image);
|
|
}
|
|
}
|
|
#endif // __APPLE__
|
|
|
|
/**
|
|
* Responds to the deprecated Carbon event types in Mac OSX.
|
|
*/
|
|
bool P3DInstance::
|
|
handle_event_osx_event_record(const P3D_event_data &event) {
|
|
bool retval = false;
|
|
|
|
#if defined(__APPLE__) && !__LP64__
|
|
assert(event._event_type == P3D_ET_osx_event_record);
|
|
EventRecord *er = event._event._osx_event_record._event;
|
|
|
|
Point pt = er->where;
|
|
|
|
// Need to ensure we have the correct port set, in order to convert the
|
|
// mouse coordinates successfully via GlobalToLocal().
|
|
const P3D_window_handle &handle = _wparams.get_parent_window();
|
|
if (handle._window_handle_type == P3D_WHT_osx_port) {
|
|
GrafPtr out_port = handle._handle._osx_port._port;
|
|
GrafPtr port_save = nullptr;
|
|
Boolean port_changed = QDSwapPort(out_port, &port_save);
|
|
|
|
GlobalToLocal(&pt);
|
|
|
|
if (port_changed) {
|
|
QDSwapPort(port_save, nullptr);
|
|
}
|
|
} else {
|
|
// First, convert the coordinates from screen coordinates to browser
|
|
// window coordinates.
|
|
WindowRef window = handle._handle._osx_cgcontext._window;
|
|
CGPoint cgpt = { (CGFloat)pt.h, (CGFloat)pt.v };
|
|
HIPointConvert(&cgpt, kHICoordSpaceScreenPixel, nullptr,
|
|
kHICoordSpaceWindow, window);
|
|
|
|
// Then convert to plugin coordinates.
|
|
pt.h = (short)(cgpt.x - _wparams.get_win_x());
|
|
pt.v = (short)(cgpt.y - _wparams.get_win_y());
|
|
}
|
|
|
|
SubprocessWindowBuffer::Event swb_event;
|
|
swb_event._source = SubprocessWindowBuffer::ES_none;
|
|
swb_event._type = SubprocessWindowBuffer::ET_none;
|
|
swb_event._code = 0;
|
|
swb_event._flags = 0;
|
|
add_carbon_modifier_flags(swb_event._flags, er->modifiers);
|
|
|
|
bool trust_mouse_data = true;
|
|
|
|
switch (er->what) {
|
|
case mouseDown:
|
|
swb_event._source = SubprocessWindowBuffer::ES_mouse;
|
|
swb_event._type = SubprocessWindowBuffer::ET_button_down;
|
|
retval = true;
|
|
break;
|
|
|
|
case mouseUp:
|
|
swb_event._source = SubprocessWindowBuffer::ES_mouse;
|
|
swb_event._type = SubprocessWindowBuffer::ET_button_up;
|
|
retval = true;
|
|
break;
|
|
|
|
case keyDown:
|
|
case keyUp:
|
|
case autoKey:
|
|
if (_swbuffer != nullptr) {
|
|
swb_event._source = SubprocessWindowBuffer::ES_keyboard;
|
|
swb_event._code = er->message;
|
|
if (er->what == keyUp) {
|
|
swb_event._type = SubprocessWindowBuffer::ET_button_up;
|
|
} else if (er->what == keyDown) {
|
|
swb_event._type = SubprocessWindowBuffer::ET_button_down;
|
|
} else {
|
|
swb_event._type = SubprocessWindowBuffer::ET_button_again;
|
|
}
|
|
retval = true;
|
|
}
|
|
break;
|
|
|
|
case updateEvt:
|
|
paint_window();
|
|
retval = true;
|
|
break;
|
|
|
|
case activateEvt:
|
|
_mouse_active = ((er->modifiers & 1) != 0);
|
|
break;
|
|
|
|
case osEvt:
|
|
// The mouse data sent with an "os event" seems to be in an indeterminate
|
|
// space.
|
|
trust_mouse_data = false;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (_mouse_active) {
|
|
swb_event._flags |= SubprocessWindowBuffer::EF_has_mouse;
|
|
if (trust_mouse_data) {
|
|
swb_event._x = pt.h;
|
|
swb_event._y = pt.v;
|
|
swb_event._flags |= SubprocessWindowBuffer::EF_mouse_position;
|
|
}
|
|
}
|
|
|
|
if (_swbuffer != nullptr) {
|
|
_swbuffer->add_event(swb_event);
|
|
}
|
|
#endif // __APPLE__
|
|
|
|
return retval;
|
|
}
|
|
|
|
/**
|
|
* Responds to the new Cocoa event types in Mac OSX.
|
|
*/
|
|
bool P3DInstance::
|
|
handle_event_osx_cocoa(const P3D_event_data &event) {
|
|
bool retval = false;
|
|
|
|
#ifdef __APPLE__
|
|
assert(event._event_type == P3D_ET_osx_cocoa);
|
|
const P3DCocoaEvent &ce = event._event._osx_cocoa._event;
|
|
|
|
SubprocessWindowBuffer::Event swb_event;
|
|
swb_event._source = SubprocessWindowBuffer::ES_none;
|
|
swb_event._type = SubprocessWindowBuffer::ET_none;
|
|
swb_event._code = 0;
|
|
swb_event._flags = 0;
|
|
|
|
switch (ce.type) {
|
|
case P3DCocoaEventDrawRect:
|
|
{
|
|
CGContextRef context = ce.data.draw.context;
|
|
paint_window_osx_cgcontext(context);
|
|
retval = true;
|
|
}
|
|
break;
|
|
|
|
case P3DCocoaEventMouseDown:
|
|
swb_event._source = SubprocessWindowBuffer::ES_mouse;
|
|
swb_event._type = SubprocessWindowBuffer::ET_button_down;
|
|
retval = true;
|
|
break;
|
|
|
|
case P3DCocoaEventMouseUp:
|
|
swb_event._source = SubprocessWindowBuffer::ES_mouse;
|
|
swb_event._type = SubprocessWindowBuffer::ET_button_up;
|
|
retval = true;
|
|
break;
|
|
|
|
case P3DCocoaEventKeyDown:
|
|
swb_event._source = SubprocessWindowBuffer::ES_keyboard;
|
|
swb_event._code = ce.data.key.keyCode << 8;
|
|
if (ce.data.key.isARepeat) {
|
|
swb_event._type = SubprocessWindowBuffer::ET_button_again;
|
|
} else {
|
|
swb_event._type = SubprocessWindowBuffer::ET_button_down;
|
|
if (ce.data.key.characters[0] > 0 & ce.data.key.characters[0] < 0x100) {
|
|
swb_event._code |= ce.data.key.characters[0];
|
|
}
|
|
}
|
|
_modifiers = ce.data.key.modifierFlags;
|
|
retval = true;
|
|
break;
|
|
|
|
case P3DCocoaEventKeyUp:
|
|
swb_event._source = SubprocessWindowBuffer::ES_keyboard;
|
|
swb_event._type = SubprocessWindowBuffer::ET_button_up;
|
|
swb_event._code = ce.data.key.keyCode << 8;
|
|
_modifiers = ce.data.key.modifierFlags;
|
|
retval = true;
|
|
break;
|
|
|
|
case P3DCocoaEventFlagsChanged:
|
|
_modifiers = ce.data.key.modifierFlags;
|
|
retval = true;
|
|
break;
|
|
|
|
case P3DCocoaEventFocusChanged:
|
|
_mouse_active = (ce.data.focus.hasFocus != 0);
|
|
retval = true;
|
|
break;
|
|
}
|
|
|
|
add_cocoa_modifier_flags(swb_event._flags, _modifiers);
|
|
|
|
switch (ce.type) {
|
|
case P3DCocoaEventMouseDown:
|
|
case P3DCocoaEventMouseMoved:
|
|
case P3DCocoaEventMouseDragged:
|
|
swb_event._x = (int)ce.data.mouse.pluginX;
|
|
swb_event._y = (int)ce.data.mouse.pluginY;
|
|
swb_event._flags |= SubprocessWindowBuffer::EF_mouse_position;
|
|
}
|
|
|
|
if (_mouse_active) {
|
|
swb_event._flags |= SubprocessWindowBuffer::EF_has_mouse;
|
|
}
|
|
|
|
if (_swbuffer != nullptr) {
|
|
_swbuffer->add_event(swb_event);
|
|
}
|
|
#endif // __APPLE__
|
|
|
|
return retval;
|
|
}
|
|
|
|
/**
|
|
* OSX only: adds the appropriate bits to the Event flag bitmask to correspond
|
|
* to the modifier buttons held in the MacOS-style EventRecord::modifiers
|
|
* mask.
|
|
*/
|
|
void P3DInstance::
|
|
add_carbon_modifier_flags(unsigned int &swb_flags, int modifiers) {
|
|
#if defined(__APPLE__) && !__LP64__
|
|
if (modifiers & cmdKey) {
|
|
swb_flags |= SubprocessWindowBuffer::EF_meta_held;
|
|
}
|
|
if (modifiers & shiftKey) {
|
|
swb_flags |= SubprocessWindowBuffer::EF_shift_held;
|
|
}
|
|
if (modifiers & optionKey) {
|
|
swb_flags |= SubprocessWindowBuffer::EF_alt_held;
|
|
}
|
|
if (modifiers & controlKey) {
|
|
swb_flags |= SubprocessWindowBuffer::EF_control_held;
|
|
}
|
|
#endif // __APPLE__
|
|
}
|
|
|
|
/**
|
|
* OSX only: adds the appropriate bits to the Event flag bitmask to correspond
|
|
* to the modifier buttons held in the P3DCocoaEvent modifierFlags mask.
|
|
*/
|
|
void P3DInstance::
|
|
add_cocoa_modifier_flags(unsigned int &swb_flags, int modifiers) {
|
|
#ifdef __APPLE__
|
|
if (modifiers & NSCommandKeyMask) {
|
|
swb_flags |= SubprocessWindowBuffer::EF_meta_held;
|
|
}
|
|
if (modifiers & NSShiftKeyMask) {
|
|
swb_flags |= SubprocessWindowBuffer::EF_shift_held;
|
|
}
|
|
if (modifiers & NSAlternateKeyMask) {
|
|
swb_flags |= SubprocessWindowBuffer::EF_alt_held;
|
|
}
|
|
if (modifiers & NSControlKeyMask) {
|
|
swb_flags |= SubprocessWindowBuffer::EF_control_held;
|
|
}
|
|
#endif // __APPLE__
|
|
}
|
|
|
|
/**
|
|
* Generates a synthetic notify message here at the C++ level.
|
|
*
|
|
* Most notify messages are generated from within the Python code, and don't
|
|
* use this method; but a few have to be sent before Python has started, and
|
|
* those come through this method.
|
|
*/
|
|
void P3DInstance::
|
|
send_notify(const string &message) {
|
|
nout << "send_notify(" << message << ")\n";
|
|
P3D_request *request = new P3D_request;
|
|
request->_instance = nullptr;
|
|
request->_request_type = P3D_RT_notify;
|
|
request->_request._notify._message = strdup(message.c_str());
|
|
add_baked_request(request);
|
|
}
|
|
|
|
#ifdef __APPLE__
|
|
/**
|
|
* OSX only: allocates the _swbuffer and associated support objects. If it
|
|
* was already allocated, deallocates the previous one first.
|
|
*/
|
|
void P3DInstance::
|
|
alloc_swbuffer() {
|
|
free_swbuffer();
|
|
|
|
int x_size = _wparams.get_win_width();
|
|
int y_size = _wparams.get_win_height();
|
|
|
|
_swbuffer = SubprocessWindowBuffer::new_buffer
|
|
(_shared_fd, _shared_mmap_size, _shared_filename, x_size, y_size);
|
|
if (_swbuffer != nullptr) {
|
|
_reversed_buffer = new char[_swbuffer->get_framebuffer_size()];
|
|
memset(_reversed_buffer, 0, _swbuffer->get_row_size());
|
|
size_t rowsize = _swbuffer->get_row_size();
|
|
|
|
_buffer_data = CFDataCreateWithBytesNoCopy(nullptr, (const UInt8 *)_reversed_buffer,
|
|
y_size * rowsize, kCFAllocatorNull);
|
|
|
|
_data_provider = CGDataProviderCreateWithCFData(_buffer_data);
|
|
_buffer_color_space = CGColorSpaceCreateDeviceRGB();
|
|
|
|
_buffer_image = CGImageCreate(x_size, y_size, 8, 32, rowsize, _buffer_color_space,
|
|
kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Little,
|
|
_data_provider, nullptr, false, kCGRenderingIntentDefault);
|
|
|
|
}
|
|
}
|
|
#endif // __APPLE__
|
|
|
|
#ifdef __APPLE__
|
|
/**
|
|
* OSX only: releases the _swbuffer and associated support objects previously
|
|
* allocated by alloc_swbuffer().
|
|
*/
|
|
void P3DInstance::
|
|
free_swbuffer() {
|
|
if (_swbuffer != nullptr) {
|
|
SubprocessWindowBuffer::destroy_buffer(_shared_fd, _shared_mmap_size,
|
|
_shared_filename, _swbuffer);
|
|
_swbuffer = nullptr;
|
|
}
|
|
if (_reversed_buffer != nullptr) {
|
|
delete[] _reversed_buffer;
|
|
_reversed_buffer = nullptr;
|
|
}
|
|
|
|
if (_buffer_image != nullptr) {
|
|
CGImageRelease(_buffer_image);
|
|
CGColorSpaceRelease(_buffer_color_space);
|
|
CGDataProviderRelease(_data_provider);
|
|
CFRelease(_buffer_data);
|
|
|
|
_buffer_data = nullptr;
|
|
_data_provider = nullptr;
|
|
_buffer_color_space = nullptr;
|
|
_buffer_image = nullptr;
|
|
}
|
|
}
|
|
#endif // __APPLE__
|
|
|
|
|
|
#ifdef __APPLE__
|
|
/**
|
|
* OSX only: this callback is associated with a CFRunLoopTimer, to be called
|
|
* periodically for updating the frame.
|
|
*/
|
|
void P3DInstance::
|
|
timer_callback(CFRunLoopTimerRef timer, void *info) {
|
|
P3DInstance *self = (P3DInstance *)info;
|
|
self->request_refresh();
|
|
}
|
|
#endif // __APPLE__
|
|
|
|
/**
|
|
*
|
|
*/
|
|
P3DInstance::ImageDownload::
|
|
ImageDownload(P3DInstance *inst, int index) :
|
|
_inst(inst),
|
|
_index(index)
|
|
{
|
|
}
|
|
|
|
/**
|
|
* Intended to be overloaded to generate a callback when the download
|
|
* finishes, either successfully or otherwise. The bool parameter is true if
|
|
* the download was successful.
|
|
*/
|
|
void P3DInstance::ImageDownload::
|
|
download_finished(bool success) {
|
|
P3DFileDownload::download_finished(success);
|
|
if (success) {
|
|
// We've successfully downloaded the image (directly, not via the package
|
|
// interface).
|
|
_inst->_image_files[_index]._filename = get_filename();
|
|
|
|
// Put it onscreen if it's supposed to be onscreen now, and our splash
|
|
// window still exists.
|
|
if (_inst->_splash_window != nullptr &&
|
|
_inst->_image_files[_index]._image_placement != P3DSplashWindow::IP_none) {
|
|
P3DSplashWindow::ImagePlacement image_placement = _inst->_image_files[_index]._image_placement;
|
|
_inst->_splash_window->set_image_filename(get_filename(), image_placement);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
P3DInstance::InstanceDownload::
|
|
InstanceDownload(P3DInstance *inst) :
|
|
_inst(inst)
|
|
{
|
|
}
|
|
|
|
/**
|
|
* Intended to be overloaded to generate an occasional callback as new data
|
|
* comes in.
|
|
*/
|
|
void P3DInstance::InstanceDownload::
|
|
download_progress() {
|
|
P3DFileDownload::download_progress();
|
|
_inst->report_instance_progress(get_download_progress(), is_download_progress_known(), get_total_data());
|
|
}
|
|
|
|
/**
|
|
* Intended to be overloaded to generate a callback when the download
|
|
* finishes, either successfully or otherwise. The bool parameter is true if
|
|
* the download was successful.
|
|
*/
|
|
void P3DInstance::InstanceDownload::
|
|
download_finished(bool success) {
|
|
P3DFileDownload::download_finished(success);
|
|
if (success) {
|
|
// We've successfully downloaded the instance data.
|
|
_inst->report_instance_progress(1.0, true, 0);
|
|
_inst->priv_set_p3d_filename(get_filename());
|
|
} else {
|
|
// Oops, no joy on the instance data.
|
|
_inst->send_notify("ondownloadfail");
|
|
_inst->set_failed();
|
|
}
|
|
}
|