diff --git a/direct/src/configfiles/panda3d.pdef b/direct/src/configfiles/panda3d.pdef index b31543d88a..fa0512c221 100755 --- a/direct/src/configfiles/panda3d.pdef +++ b/direct/src/configfiles/panda3d.pdef @@ -1,7 +1,7 @@ # This file defines a number of standard "packages" that correspond to # a Panda3D distribution. These packages are built by passing this # file to the ppackage utility, either as a packaged application, or -# as the module direct.showutil.ppackage. +# as the module direct.p3d.ppackage. # These packages are then downloaded by the Panda3D plugin and # standalone runtime executable, and they contain the actual Panda3D @@ -22,7 +22,7 @@ config display_name="Panda3D" # This is the key Python module that is imported at runtime to start # an application running. -module direct.showutil.runp3d +module direct.p3d.AppRunner # These are additional Python modules that are needed by most Panda3D # applications. It doesn't matter too much if we miss one or two @@ -59,7 +59,7 @@ freeze_dll runp3d_frozen # This is the main program that drives the plugin application. It is # responsible for loading runp3d_frozen, above, and then importing -# direct.showutil.runp3d, to start an application running. Note that +# direct.p3d.runp3d, to start an application running. Note that # the .exe extension is automatically replaced with the # platform-specific extension appropriate for an executable. file p3dpython.exe @@ -147,7 +147,7 @@ config hidden=1 require panda3d require egg -main_module direct.showutil.packp3d +main_module direct.p3d.packp3d end_p3d packp3d @@ -164,6 +164,6 @@ config hidden=1 require panda3d require egg -main_module direct.showutil.ppackage +main_module direct.p3d.ppackage end_p3d ppackage diff --git a/direct/src/showutil/JavaScript.py b/direct/src/p3d/JavaScript.py similarity index 100% rename from direct/src/showutil/JavaScript.py rename to direct/src/p3d/JavaScript.py diff --git a/direct/src/showutil/Packager.py b/direct/src/p3d/Packager.py similarity index 100% rename from direct/src/showutil/Packager.py rename to direct/src/p3d/Packager.py diff --git a/direct/src/p3d/Sources.pp b/direct/src/p3d/Sources.pp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/direct/src/p3d/__init__.py b/direct/src/p3d/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/direct/src/showutil/make_contents.py b/direct/src/p3d/make_contents.py similarity index 100% rename from direct/src/showutil/make_contents.py rename to direct/src/p3d/make_contents.py diff --git a/direct/src/showutil/packp3d.py b/direct/src/p3d/packp3d.py similarity index 99% rename from direct/src/showutil/packp3d.py rename to direct/src/p3d/packp3d.py index 5663849584..d18092f9d9 100755 --- a/direct/src/showutil/packp3d.py +++ b/direct/src/p3d/packp3d.py @@ -47,7 +47,7 @@ import os import getopt import glob import direct -from direct.showutil import Packager +from direct.p3d import Packager from pandac.PandaModules import * class ArgumentError(StandardError): diff --git a/direct/src/showutil/ppackage.py b/direct/src/p3d/ppackage.py similarity index 98% rename from direct/src/showutil/ppackage.py rename to direct/src/p3d/ppackage.py index 4419548146..a37596fd88 100755 --- a/direct/src/showutil/ppackage.py +++ b/direct/src/p3d/ppackage.py @@ -86,8 +86,8 @@ import sys import getopt import os -from direct.showutil import Packager -from direct.showutil import make_contents +from direct.p3d import Packager +from direct.p3d import make_contents from pandac.PandaModules import * def usage(code, msg = ''): diff --git a/direct/src/p3d/runp3d.py b/direct/src/p3d/runp3d.py new file mode 100644 index 0000000000..02d54ebc34 --- /dev/null +++ b/direct/src/p3d/runp3d.py @@ -0,0 +1,68 @@ +#! /usr/bin/env python + +""" + +This tool will invoke the AppRunner to execute a packaged p3d +application. It requires that that the current Panda3D and Python +versions match the version expected by the application. + +Normally, you do not need to use this tool; instead, use the provided +standalone panda3d executable to invoke any p3d application. Using +panda3d will guarantee that the correct versions of Panda3D and Python +are used to run the application. However, there may be occasions when +it is useful to use this tool to run the application with the current +build instead of with its advertised version requirements. + +Usage: + + runp3d.py app.p3d [args] + +The command-line arguments following the application name are passed +into the application unchanged. + +See pack3d.p3d for an application that generates these p3d files. + +""" + +import sys +import getopt +from AppRunner import AppRunner, ArgumentError +from direct.task.TaskManagerGlobal import taskMgr +from pandac.PandaModules import Filename + +def parseSysArgs(): + """ Handles sys.argv, if there are any local arguments, and + returns a new argv suitable for passing into the + application. """ + + # We prefix a "+" sign, following the GNU convention, to tell + # getopt not to parse options following the first non-option + # parameter. + opts, args = getopt.getopt(sys.argv[1:], '+h') + + for option, value in opts: + if option == '-h': + print __doc__ + sys.exit(1) + + if not args or not args[0]: + raise ArgumentError, "No Panda app specified. Use:\nrunp3d.py app.p3d" + + arg0 = args[0] + p3dFilename = Filename.fromOsSpecific(arg0) + if p3dFilename.exists(): + p3dFilename.makeAbsolute() + arg0 = p3dFilename.toOsSpecific() + + return [arg0] + args[1:] + +if __name__ == '__main__': + runner = AppRunner() + runner.gotWindow = True + try: + argv = parseSysArgs() + runner.setP3DFilename(argv[0], argv = argv) + except ArgumentError, e: + print e.args[0] + sys.exit(1) + taskMgr.run() diff --git a/direct/src/plugin/binaryXml.cxx b/direct/src/plugin/binaryXml.cxx index c046c5739e..3af1b2df1e 100644 --- a/direct/src/plugin/binaryXml.cxx +++ b/direct/src/plugin/binaryXml.cxx @@ -27,6 +27,11 @@ enum NodeType { NT_text, }; +// These are both prime numbers, though I don't know if that really +// matters. Mainly, they're big random numbers. +static const size_t length_nonce1 = 812311453; +static const size_t length_nonce2 = 612811373; + //////////////////////////////////////////////////////////////////// // Function: write_xml_node // Description: Recursively writes a node and all of its children to @@ -53,7 +58,15 @@ write_xml_node(ostream &out, TiXmlNode *xnode) { const string &value = xnode->ValueStr(); size_t value_length = value.length(); + size_t value_proof = (value_length + length_nonce1) * length_nonce2; + + // We write out not only value_length, but the same value again + // hashed by length_nonce1 and 2 (and truncated back to size_t), + // just to prove to the reader that we're still on the same page. + // We do this only on the top node; we don't bother for the nested + // nodes. out.write((char *)&value_length, sizeof(value_length)); + out.write((char *)&value_proof, sizeof(value_proof)); out.write(value.data(), value_length); if (type == NT_element) { @@ -104,7 +117,8 @@ write_xml_node(ostream &out, TiXmlNode *xnode) { // return value. Returns NULL on error. //////////////////////////////////////////////////////////////////// static TiXmlNode * -read_xml_node(istream &in, char *&buffer, size_t &buffer_length) { +read_xml_node(istream &in, char *&buffer, size_t &buffer_length, + ostream &logfile) { NodeType type = (NodeType)in.get(); if (type == NT_unknown) { return NULL; @@ -115,6 +129,34 @@ read_xml_node(istream &in, char *&buffer, size_t &buffer_length) { if (in.gcount() != sizeof(value_length)) { return NULL; } + size_t value_proof_expect = (value_length + length_nonce1) * length_nonce2; + size_t value_proof; + in.read((char *)&value_proof, sizeof(value_proof)); + if (in.gcount() != sizeof(value_proof)) { + return NULL; + } + if (value_proof != value_proof_expect) { + // Hey, we ran into garbage: the proof value didn't match our + // expected proof value. + logfile << "Garbage on XML stream!\n"; + + // Print out the garbage; maybe it will help the developer figure + // out where it came from. + logfile << "Begin garbage:\n"; + ostringstream strm; + strm.write((char *)&value_length, sizeof(value_length)); + strm.write((char *)&value_proof, sizeof(value_proof)); + logfile << strm.str(); + for (size_t i = 0; i < 100; ++i) { + int ch = in.get(); + if (ch != EOF) { + logfile.put(ch); + } + } + logfile << "\n"; + logfile << "End garbage.\n"; + return NULL; + } if (value_length > buffer_length) { delete[] buffer; @@ -187,7 +229,7 @@ read_xml_node(istream &in, char *&buffer, size_t &buffer_length) { while (got_child && in && !in.eof()) { // We have a child. - TiXmlNode *xchild = read_xml_node(in, buffer, buffer_length); + TiXmlNode *xchild = read_xml_node(in, buffer, buffer_length, logfile); if (xchild != NULL) { xnode->LinkEndChild(xchild); } @@ -251,7 +293,7 @@ read_xml(istream &in, ostream &logfile) { // binary read. size_t buffer_length = 128; char *buffer = new char[buffer_length]; - TiXmlNode *xnode = read_xml_node(in, buffer, buffer_length); + TiXmlNode *xnode = read_xml_node(in, buffer, buffer_length, logfile); delete[] buffer; if (xnode == NULL) { return NULL; diff --git a/direct/src/plugin/p3dFileParams.cxx b/direct/src/plugin/p3dFileParams.cxx index 8b324d5fce..a0773e06e8 100644 --- a/direct/src/plugin/p3dFileParams.cxx +++ b/direct/src/plugin/p3dFileParams.cxx @@ -164,7 +164,7 @@ TiXmlElement *P3DFileParams:: make_xml() { TiXmlElement *xfparams = new TiXmlElement("fparams"); - xfparams->SetAttribute("p3d_filename", _p3d_filename.c_str()); + xfparams->SetAttribute("p3d_filename", _p3d_filename); Tokens::const_iterator ti; for (ti = _tokens.begin(); ti != _tokens.end(); ++ti) { diff --git a/direct/src/plugin/p3dHost.cxx b/direct/src/plugin/p3dHost.cxx index 0714d794cc..106c5e67df 100644 --- a/direct/src/plugin/p3dHost.cxx +++ b/direct/src/plugin/p3dHost.cxx @@ -35,7 +35,7 @@ P3DHost(const string &host_url) : _xcontents = NULL; - fill_host_dir(); + determine_host_dir(); } //////////////////////////////////////////////////////////////////// @@ -196,14 +196,17 @@ get_package_desc_file(FileSpec &desc_file, // out } //////////////////////////////////////////////////////////////////// -// Function: P3DHost::fill_host_dir +// Function: P3DHost::determine_host_dir // Access: Private // Description: Hashes the host_url into a (mostly) unique directory -// string for this particular host. Stores the result -// in _host_dir. +// string, which will be the root of the host's install +// tree. Stores the result in _host_dir. +// +// This code is duplicated in Python, in +// AppRunner.determineHostDir(). //////////////////////////////////////////////////////////////////// void P3DHost:: -fill_host_dir() { +determine_host_dir() { P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr(); _host_dir = inst_mgr->get_root_dir(); _host_dir += "/"; @@ -213,6 +216,9 @@ fill_host_dir() { // Look for a server name in the URL. Including this string in the // directory name makes it friendlier for people browsing the // directory. + + // We can't use URLSpec here, because we don't link with Panda3D. + // We have to do it by hand. size_t p = _host_url.find("://"); if (p != string::npos) { size_t start = p + 3; @@ -254,7 +260,7 @@ fill_host_dir() { // If we successfully got a hostname, we don't really need the // full hash. We'll keep half of it. - keep_hash = hash_size / 2; + keep_hash = keep_hash / 2; } MD5_CTX ctx; diff --git a/direct/src/plugin/p3dHost.h b/direct/src/plugin/p3dHost.h index f616c95ae7..27e17c7d70 100644 --- a/direct/src/plugin/p3dHost.h +++ b/direct/src/plugin/p3dHost.h @@ -51,7 +51,7 @@ public: const string &package_version); private: - void fill_host_dir(); + void determine_host_dir(); static string standardize_filename(const string &filename); static bool copy_file(const string &from_filename, const string &to_filename); diff --git a/direct/src/plugin/p3dInstance.cxx b/direct/src/plugin/p3dInstance.cxx index 70be979dc7..f6d085dfbb 100644 --- a/direct/src/plugin/p3dInstance.cxx +++ b/direct/src/plugin/p3dInstance.cxx @@ -842,6 +842,9 @@ make_xml() { 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()); + TiXmlElement *xfparams = _fparams.make_xml(); xinstance->LinkEndChild(xfparams); @@ -850,6 +853,12 @@ make_xml() { xinstance->LinkEndChild(xwparams); } + Packages::const_iterator pi; + for (pi = _packages.begin(); pi != _packages.end(); ++pi) { + TiXmlElement *xpackage = (*pi)->make_xml(); + xinstance->LinkEndChild(xpackage); + } + return xinstance; } diff --git a/direct/src/plugin/p3dPackage.cxx b/direct/src/plugin/p3dPackage.cxx index 0b29765ad4..8d09de22ac 100755 --- a/direct/src/plugin/p3dPackage.cxx +++ b/direct/src/plugin/p3dPackage.cxx @@ -129,6 +129,30 @@ remove_instance(P3DInstance *inst) { begin_info_download(); } +//////////////////////////////////////////////////////////////////// +// Function: P3DPackage::make_xml +// Access: Public +// Description: Returns a newly-allocated XML structure that +// corresponds to the package data within this +// instance. +//////////////////////////////////////////////////////////////////// +TiXmlElement *P3DPackage:: +make_xml() { + TiXmlElement *xpackage = new TiXmlElement("package"); + + xpackage->SetAttribute("name", _package_name); + if (!_package_platform.empty()) { + xpackage->SetAttribute("platform", _package_platform); + } + if (!_package_version.empty()) { + xpackage->SetAttribute("version", _package_version); + } + xpackage->SetAttribute("host", _host->get_host_url()); + xpackage->SetAttribute("install_dir", _package_dir); + + return xpackage; +} + //////////////////////////////////////////////////////////////////// // Function: P3DPackage::begin_info_download // Access: Private diff --git a/direct/src/plugin/p3dPackage.h b/direct/src/plugin/p3dPackage.h index c1d65db8f2..813813a4e4 100755 --- a/direct/src/plugin/p3dPackage.h +++ b/direct/src/plugin/p3dPackage.h @@ -60,6 +60,8 @@ public: void add_instance(P3DInstance *inst); void remove_instance(P3DInstance *inst); + TiXmlElement *make_xml(); + private: enum DownloadType { DT_contents_file, diff --git a/direct/src/plugin/p3dPythonRun.cxx b/direct/src/plugin/p3dPythonRun.cxx index 175c31487d..3eb23b9c48 100755 --- a/direct/src/plugin/p3dPythonRun.cxx +++ b/direct/src/plugin/p3dPythonRun.cxx @@ -127,14 +127,14 @@ run_python() { Py_DECREF(runp3d_frozen); // So now we can import the module itself. - PyObject *runp3d = PyImport_ImportModule("direct.showutil.runp3d"); - if (runp3d == NULL) { + PyObject *app_runner_module = PyImport_ImportModule("direct.p3d.AppRunner"); + if (app_runner_module == NULL) { PyErr_Print(); return false; } // Get the pointers to the objects needed within the module. - PyObject *app_runner_class = PyObject_GetAttrString(runp3d, "AppRunner"); + PyObject *app_runner_class = PyObject_GetAttrString(app_runner_module, "AppRunner"); if (app_runner_class == NULL) { PyErr_Print(); return false; @@ -149,41 +149,41 @@ run_python() { Py_DECREF(app_runner_class); // Get the UndefinedObject class. - _undefined_object_class = PyObject_GetAttrString(runp3d, "UndefinedObject"); + _undefined_object_class = PyObject_GetAttrString(app_runner_module, "UndefinedObject"); if (_undefined_object_class == NULL) { PyErr_Print(); return false; } // And the "Undefined" instance. - _undefined = PyObject_GetAttrString(runp3d, "Undefined"); + _undefined = PyObject_GetAttrString(app_runner_module, "Undefined"); if (_undefined == NULL) { PyErr_Print(); return false; } // Get the ConcreteStruct class. - _concrete_struct_class = PyObject_GetAttrString(runp3d, "ConcreteStruct"); + _concrete_struct_class = PyObject_GetAttrString(app_runner_module, "ConcreteStruct"); if (_concrete_struct_class == NULL) { PyErr_Print(); return false; } // Get the BrowserObject class. - _browser_object_class = PyObject_GetAttrString(runp3d, "BrowserObject"); + _browser_object_class = PyObject_GetAttrString(app_runner_module, "BrowserObject"); if (_browser_object_class == NULL) { PyErr_Print(); return false; } // Get the global TaskManager. - _taskMgr = PyObject_GetAttrString(runp3d, "taskMgr"); + _taskMgr = PyObject_GetAttrString(app_runner_module, "taskMgr"); if (_taskMgr == NULL) { PyErr_Print(); return false; } - Py_DECREF(runp3d); + Py_DECREF(app_runner_module); // Construct a Python wrapper around our methods we need to expose to Python. @@ -903,6 +903,14 @@ void P3DPythonRun:: start_instance(P3DCInstance *inst, TiXmlElement *xinstance) { _instances[inst->get_instance_id()] = inst; + set_instance_info(inst, xinstance); + + TiXmlElement *xpackage = xinstance->FirstChildElement("package"); + while (xpackage != (TiXmlElement *)NULL) { + add_package_info(inst, xpackage); + xpackage = xpackage->NextSiblingElement("package"); + } + TiXmlElement *xfparams = xinstance->FirstChildElement("fparams"); if (xfparams != (TiXmlElement *)NULL) { set_p3d_filename(inst, xfparams); @@ -937,6 +945,59 @@ terminate_instance(int id) { terminate_session(); } +//////////////////////////////////////////////////////////////////// +// Function: P3DPythonRun::set_instance_info +// Access: Private +// Description: Sets some global information about the instance. +//////////////////////////////////////////////////////////////////// +void P3DPythonRun:: +set_instance_info(P3DCInstance *inst, TiXmlElement *xinstance) { + const char *root_dir = xinstance->Attribute("root_dir"); + if (root_dir == NULL) { + root_dir = ""; + } + + PyObject *result = PyObject_CallMethod + (_runner, (char *)"setInstanceInfo", (char *)"s", root_dir); + + if (result == NULL) { + PyErr_Print(); + } + Py_XDECREF(result); +} + +//////////////////////////////////////////////////////////////////// +// Function: P3DPythonRun::add_package_info +// Access: Private +// Description: Adds some information about a pre-loaded package. +//////////////////////////////////////////////////////////////////// +void P3DPythonRun:: +add_package_info(P3DCInstance *inst, TiXmlElement *xpackage) { + const char *name = xpackage->Attribute("name"); + const char *platform = xpackage->Attribute("platform"); + const char *version = xpackage->Attribute("version"); + const char *host = xpackage->Attribute("host"); + const char *install_dir = xpackage->Attribute("install_dir"); + if (name == NULL || version == NULL || host == NULL) { + return; + } + if (platform == NULL) { + platform = ""; + } + if (install_dir == NULL) { + install_dir = ""; + } + + PyObject *result = PyObject_CallMethod + (_runner, (char *)"addPackageInfo", (char *)"sssss", + name, platform, version, host, install_dir); + + if (result == NULL) { + PyErr_Print(); + } + Py_XDECREF(result); +} + //////////////////////////////////////////////////////////////////// // Function: P3DPythonRun::set_p3d_filename // Access: Private diff --git a/direct/src/plugin/p3dPythonRun.h b/direct/src/plugin/p3dPythonRun.h index 3d60f90781..b920e54eab 100755 --- a/direct/src/plugin/p3dPythonRun.h +++ b/direct/src/plugin/p3dPythonRun.h @@ -87,6 +87,8 @@ private: void start_instance(P3DCInstance *inst, TiXmlElement *xinstance); void terminate_instance(int id); + void set_instance_info(P3DCInstance *inst, TiXmlElement *xinstance); + void add_package_info(P3DCInstance *inst, TiXmlElement *xpackage); void set_p3d_filename(P3DCInstance *inst, TiXmlElement *xfparams); void setup_window(int id, TiXmlElement *xwparams); void setup_window(P3DCInstance *inst, TiXmlElement *xwparams); diff --git a/direct/src/showutil/.gitignore b/direct/src/showutil/.gitignore deleted file mode 100644 index 1e7f41c0c3..0000000000 --- a/direct/src/showutil/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -/packp3d -/packp3d.bat -/runp3d -/runp3d.bat diff --git a/direct/src/showutil/runp3d.py b/direct/src/showutil/runp3d.py deleted file mode 100755 index c3bc52ec6d..0000000000 --- a/direct/src/showutil/runp3d.py +++ /dev/null @@ -1,516 +0,0 @@ -#! /usr/bin/env python - -""" - -This module is intended to be compiled into the Panda3D runtime -distributable, but it can also be run directly via the Python -interpreter. It will run a Panda3D applet--a p3d file--that has -previously been generated via packp3d.py. - -Usage: - - runp3d.py app.p3d [args] - -The command-line arguments following the application name are passed -into the application unchanged. - -See pack3d.py for a script that generates these p3d files. - -""" - -import sys -from direct.showbase import VFSImporter -from direct.showbase.DirectObject import DirectObject -from pandac.PandaModules import VirtualFileSystem, Filename, Multifile, loadPrcFileData, unloadPrcFile, getModelPath, HTTPClient, Thread, WindowProperties, readXmlStream, ExecutionEnvironment -from direct.stdpy import file -from direct.task.TaskManagerGlobal import taskMgr -from direct.showbase.MessengerGlobal import messenger -from direct.showbase import AppRunnerGlobal - -# These imports are read by the C++ wrapper in p3dPythonRun.cxx. -from direct.showutil.JavaScript import UndefinedObject, Undefined, ConcreteStruct, BrowserObject - -import os -import types -import __builtin__ - -class ArgumentError(AttributeError): - pass - -class ScriptAttributes: - """ This dummy class serves as the root object for the scripting - interface. The Python code can store objects and functions here - for direct inspection by the browser's JavaScript code. """ - pass - -class AppRunner(DirectObject): - def __init__(self): - DirectObject.__init__(self) - - self.sessionId = 0 - self.packedAppEnvironmentInitialized = False - self.gotWindow = False - self.gotP3DFilename = False - self.started = False - self.windowOpened = False - self.windowPrc = None - - self.fullDiskAccess = False - - self.Undefined = Undefined - self.ConcreteStruct = ConcreteStruct - - # This is per session. - self.nextScriptId = 0 - - # TODO: we need one of these per instance, not per session. - self.instanceId = None - - # The mount point for the multifile. For now, this is always - # the same, but when we move to multiple-instance sessions, it - # may have to be different for each instance. - self.multifileRoot = '/mf' - - # The "main" object will be exposed to the DOM as a property - # of the plugin object; that is, document.pluginobject.main in - # JavaScript will be appRunner.main here. - self.main = ScriptAttributes() - - # By default, we publish a stop() method so the browser can - # easy stop the plugin. - self.main.stop = self.stop - - # This will be the browser's toplevel window DOM object; - # e.g. self.dom.document will be the document. - self.dom = None - - # This is the list of expressions we will evaluate when - # self.dom gets assigned. - self.deferredEvals = [] - - # This is the default requestFunc that is installed if we - # never call setRequestFunc(). - def defaultRequestFunc(*args): - if args[1] == 'notify': - # Quietly ignore notifies. - return - print "Ignoring request: %s" % (args,) - self.requestFunc = defaultRequestFunc - - # Store our pointer so DirectStart-based apps can find us. - if AppRunnerGlobal.appRunner is None: - AppRunnerGlobal.appRunner = self - - # We use this messenger hook to dispatch this startIfReady() - # call back to the main thread. - self.accept('startIfReady', self.startIfReady) - - def stop(self): - """ This method can be called by JavaScript to stop the - application. """ - - # We defer the actual exit for a few frames, so we don't raise - # an exception and invalidate the JavaScript call; and also to - # help protect against race conditions as the application - # shuts down. - taskMgr.doMethodLater(0.5, sys.exit, 'exit') - - def setSessionId(self, sessionId): - """ This message should come in at startup. """ - self.sessionId = sessionId - self.nextScriptId = self.sessionId * 1000 + 10000 - - def initPackedAppEnvironment(self): - """ This function sets up the Python environment suitably for - running a packed app. It should only run once in any given - session (and it includes logic to ensure this). """ - - if self.packedAppEnvironmentInitialized: - return - - self.packedAppEnvironmentInitialized = True - - # We need to make sure sys.stdout maps to sys.stderr instead, so - # if someone makes an unadorned print command within Python code, - # it won't muck up the data stream between parent and child. - sys.stdout = sys.stderr - - vfs = VirtualFileSystem.getGlobalPtr() - - # Unmount directories we don't need. This doesn't provide - # actual security, since it only disables this stuff for users - # who go through the vfs; a malicious programmer can always - # get to the underlying true file I/O operations. Still, it - # can help prevent honest developers from accidentally getting - # stuck where they don't belong. - if not self.fullDiskAccess: - # Clear *all* the mount points, including "/", so that we - # no longer access the disk directly. - vfs.unmountAll() - - # Make sure the directories on our standard Python path - # are mounted read-only, so we can still load Python. - # Note: read-only actually doesn't have any effect on the - # vfs right now; careless application code can still write - # to these directories inadvertently. - for dirname in sys.path: - dirname = Filename.fromOsSpecific(dirname) - if dirname.isDirectory(): - vfs.mount(dirname, dirname, vfs.MFReadOnly) - - # Also mount some standard directories read-write - # (temporary and app-data directories). - tdir = Filename.temporary('', '') - for dirname in set([ tdir.getDirname(), - Filename.getTempDirectory().cStr(), - Filename.getUserAppdataDirectory().cStr(), - Filename.getCommonAppdataDirectory().cStr() ]): - vfs.mount(dirname, dirname, 0) - - # And we might need the current working directory. - dirname = ExecutionEnvironment.getCwd() - vfs.mount(dirname, dirname, 0) - - # Now set up Python to import this stuff. - VFSImporter.register() - sys.path = [ self.multifileRoot ] + sys.path - - # Put our root directory on the model-path, too. - getModelPath().prependDirectory(self.multifileRoot) - - # Replace the builtin open and file symbols so user code will get - # our versions by default, which can open and read files out of - # the multifile. - __builtin__.file = file.file - __builtin__.open = file.open - os.listdir = file.listdir - os.walk = file.walk - - if not self.fullDiskAccess: - # Make "/mf" our "current directory", for running the multifiles - # we plan to mount there. - vfs.chdir(self.multifileRoot) - - def startIfReady(self): - if self.started: - return - - if self.gotWindow and self.gotP3DFilename: - self.started = True - - # Now we can ignore future calls to startIfReady(). - self.ignore('startIfReady') - - # Hang a hook so we know when the window is actually opened. - self.acceptOnce('window-event', self.windowEvent) - - # Look for the startup Python file. This may be a magic - # filename (like "__main__", or any filename that contains - # invalid module characters), so we can't just import it - # directly; instead, we go through the low-level importer. - - # If there's no p3d_info.xml file, we look for "main". - moduleName = 'main' - if self.p3dPackage: - mainName = self.p3dPackage.Attribute('main_module') - if mainName: - moduleName = mainName - - root = self.multifileRoot - if '.' in moduleName: - root += '/' + '/'.join(moduleName.split('.')[:-1]) - v = VFSImporter.VFSImporter(root) - loader = v.find_module(moduleName) - if not loader: - message = "No %s found in application." % (moduleName) - raise StandardError, message - - main = loader.load_module(moduleName) - if hasattr(main, 'main') and callable(main.main): - main.main(self) - - def getPandaScriptObject(self): - """ Called by the browser to query the Panda instance's - toplevel scripting object, for querying properties in the - Panda instance. The attributes on this object are mapped to - document.pluginobject.main within the DOM. """ - - return self.main - - def setBrowserScriptObject(self, dom): - """ Called by the browser to supply the browser's toplevel DOM - object, for controlling the JavaScript and the document in the - same page with the Panda3D plugin. """ - - self.dom = dom - - # Now evaluate any deferred expressions. - for expression in self.deferredEvals: - self.scriptRequest('eval', self.dom, value = expression, - needsResponse = False) - self.deferredEvals = [] - - def setP3DFilename(self, p3dFilename, tokens = [], argv = [], - instanceId = None): - # One day we will have support for multiple instances within a - # Python session. Against that day, we save the instance ID - # for this instance. - self.instanceId = instanceId - - self.tokens = tokens - self.tokenDict = dict(tokens) - self.argv = argv - - # Also store the arguments on sys, for applications that - # aren't instance-ready. - sys.argv = argv - - # Tell the browser that Python is up and running, and ready to - # respond to queries. - self.notifyRequest('onpythonload') - - # Now go load the applet. - fname = Filename.fromOsSpecific(p3dFilename) - vfs = VirtualFileSystem.getGlobalPtr() - - if not vfs.exists(fname): - raise ArgumentError, "No such file: %s" % (p3dFilename) - - fname.makeAbsolute() - mf = Multifile() - if not mf.openRead(fname): - raise ArgumentError, "Not a Panda Multifile: %s" % (p3dFilename) - - # Now load the p3dInfo file. - self.p3dInfo = None - self.p3dPackage = None - i = mf.findSubfile('p3d_info.xml') - if i >= 0: - stream = mf.openReadSubfile(i) - self.p3dInfo = readXmlStream(stream) - mf.closeReadSubfile(stream) - if self.p3dInfo: - self.p3dPackage = self.p3dInfo.FirstChildElement('package') - - if self.p3dPackage: - fullDiskAccess = self.p3dPackage.Attribute('full_disk_access') - try: - self.fullDiskAccess = int(fullDiskAccess or '') - except ValueError: - pass - - self.initPackedAppEnvironment() - - # Mount the Multifile under /mf, by convention. - vfs.mount(mf, self.multifileRoot, vfs.MFReadOnly) - VFSImporter.freeze_new_modules(mf, self.multifileRoot) - - # Load any prc files in the root. We have to load them - # explicitly, since the ConfigPageManager can't directly look - # inside the vfs. Use the Multifile interface to find the prc - # files, rather than vfs.scanDirectory(), so we only pick up the - # files in this particular multifile. - for f in mf.getSubfileNames(): - fn = Filename(f) - if fn.getDirname() == '' and fn.getExtension() == 'prc': - pathname = '%s/%s' % (self.multifileRoot, f) - data = open(pathname, 'r').read() - loadPrcFileData(pathname, data) - - self.gotP3DFilename = True - - # Send this call to the main thread; don't call it directly. - messenger.send('startIfReady', taskChain = 'default') - - def clearWindowPrc(self): - """ Clears the windowPrc file that was created in a previous - call to setupWindow(), if any. """ - - if self.windowPrc: - unloadPrcFile(self.windowPrc) - self.windowPrc = None - - def setupWindow(self, windowType, x, y, width, height, - parent, subprocessWindow): - """ Applies the indicated window parameters to the prc - settings, for future windows; or applies them directly to the - main window if the window has already been opened. """ - - if self.started and base.win: - # If we've already got a window, this must be a - # resize/reposition request. - wp = WindowProperties() - if x or y or windowType == 'embedded': - wp.setOrigin(x, y) - if width or height: - wp.setSize(width, height) - if subprocessWindow: - wp.setSubprocessWindow(subprocessWindow) - base.win.requestProperties(wp) - return - - # If we haven't got a window already, start 'er up. Apply the - # requested setting to the prc file. - - if windowType == 'hidden': - data = 'window-type none\n' - else: - data = 'window-type onscreen\n' - - if windowType == 'fullscreen': - data += 'fullscreen 1\n' - else: - data += 'fullscreen 0\n' - - if windowType == 'embedded': - data += 'parent-window-handle %s\nsubprocess-window %s\n' % ( - parent, subprocessWindow) - else: - data += 'parent-window-handle 0\nsubprocess-window \n' - - if x or y or windowType == 'embedded': - data += 'win-origin %s %s\n' % (x, y) - if width or height: - data += 'win-size %s %s\n' % (width, height) - - self.clearWindowPrc() - self.windowPrc = loadPrcFileData("setupWindow", data) - - self.gotWindow = True - - # Send this call to the main thread; don't call it directly. - messenger.send('startIfReady', taskChain = 'default') - - def setRequestFunc(self, func): - """ This method is called by the plugin at startup to supply a - function that can be used to deliver requests upstream, to the - plugin, and thereby to the browser. """ - self.requestFunc = func - - def sendRequest(self, request, *args): - """ Delivers a request to the browser via self.requestFunc. - This low-level function is not intended to be called directly - by user code. """ - - assert self.requestFunc - return self.requestFunc(self.instanceId, request, args) - - def windowEvent(self, win): - """ This method is called when we get a window event. We - listen for this to detect when the window has been - successfully opened. """ - - if not self.windowOpened: - self.windowOpened = True - - # Now that the window is open, we don't need to keep those - # prc settings around any more. - self.clearWindowPrc() - - # Inform the plugin and browser. - self.notifyRequest('onwindowopen') - - def notifyRequest(self, message): - """ Delivers a notify request to the browser. This is a "this - happened" type notification; it also triggers some JavaScript - code execution, if indicated in the HTML tags, and may also - trigger some internal automatic actions. (For instance, the - plugin takes down the splash window when it sees the - onwindowopen notification. """ - - self.sendRequest('notify', message) - - def evalScript(self, expression, needsResponse = False): - """ Evaluates an arbitrary JavaScript expression in the global - DOM space. This may be deferred if necessary if needsResponse - is False and self.dom has not yet been assigned. If - needsResponse is true, this waits for the value and returns - it, which means it cannot be deferred. """ - - if not self.dom: - # Defer the expression. - assert not needsResponse - self.deferredEvals.append(expression) - else: - # Evaluate it now. - return self.scriptRequest('eval', self.dom, value = expression, - needsResponse = needsResponse) - - def scriptRequest(self, operation, object, propertyName = '', - value = None, needsResponse = True): - """ Issues a new script request to the browser. This queries - or modifies one of the browser's DOM properties. - - operation may be one of [ 'get_property', 'set_property', - 'call', 'evaluate' ]. - - object is the browser object to manipulate, or the scope in - which to evaluate the expression. - - propertyName is the name of the property to manipulate, if - relevant (set to None for the default method name). - - value is the new value to assign to the property for - set_property, or the parameter list for call, or the string - expression for evaluate. - - If needsResponse is true, this method will block until the - return value is received from the browser, and then it returns - that value. Otherwise, it returns None immediately, without - waiting for the browser to process the request. - """ - uniqueId = self.nextScriptId - self.nextScriptId = (self.nextScriptId + 1) % 0xffffffff - self.sendRequest('script', operation, object, - propertyName, value, needsResponse, uniqueId) - - if needsResponse: - # Now wait for the response to come in. - result = self.sendRequest('wait_script_response', uniqueId) - return result - - def dropObject(self, objectId): - """ Inform the parent process that we no longer have an - interest in the P3D_object corresponding to the indicated - objectId. """ - - self.sendRequest('drop_p3dobj', objectId) - - def parseSysArgs(self): - """ Handles sys.argv, if there are any local arguments, and - returns a new argv suitable for passing into the - application. """ - - import getopt - - # We prefix a "+" sign, following the GNU convention, to tell - # getopt not to parse options following the first non-option - # parameter. - opts, args = getopt.getopt(sys.argv[1:], '+h') - - for option, value in opts: - if option == '-h': - print __doc__ - sys.exit(1) - - if not args or not args[0]: - raise ArgumentError, "No Panda app specified. Use:\nrunp3d.py app.p3d" - - arg0 = args[0] - p3dFilename = Filename.fromOsSpecific(arg0) - if p3dFilename.exists(): - p3dFilename.makeAbsolute() - arg0 = p3dFilename.toOsSpecific() - - return [arg0] + args[1:] - -if __name__ == '__main__': - runner = AppRunner() - runner.gotWindow = True - try: - argv = runner.parseSysArgs() - runner.setP3DFilename(argv[0], argv = argv) - except ArgumentError, e: - print e.args[0] - sys.exit(1) - taskMgr.run()