move code to p3d directory for better organization

This commit is contained in:
David Rose 2009-08-23 02:00:03 +00:00
parent 01f032a0a5
commit d8859828ef
20 changed files with 242 additions and 548 deletions

View File

@ -1,7 +1,7 @@
# This file defines a number of standard "packages" that correspond to # This file defines a number of standard "packages" that correspond to
# a Panda3D distribution. These packages are built by passing this # a Panda3D distribution. These packages are built by passing this
# file to the ppackage utility, either as a packaged application, or # 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 # These packages are then downloaded by the Panda3D plugin and
# standalone runtime executable, and they contain the actual Panda3D # 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 # This is the key Python module that is imported at runtime to start
# an application running. # an application running.
module direct.showutil.runp3d module direct.p3d.AppRunner
# These are additional Python modules that are needed by most Panda3D # These are additional Python modules that are needed by most Panda3D
# applications. It doesn't matter too much if we miss one or two # 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 # This is the main program that drives the plugin application. It is
# responsible for loading runp3d_frozen, above, and then importing # 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 # the .exe extension is automatically replaced with the
# platform-specific extension appropriate for an executable. # platform-specific extension appropriate for an executable.
file p3dpython.exe file p3dpython.exe
@ -147,7 +147,7 @@ config hidden=1
require panda3d require panda3d
require egg require egg
main_module direct.showutil.packp3d main_module direct.p3d.packp3d
end_p3d packp3d end_p3d packp3d
@ -164,6 +164,6 @@ config hidden=1
require panda3d require panda3d
require egg require egg
main_module direct.showutil.ppackage main_module direct.p3d.ppackage
end_p3d ppackage end_p3d ppackage

View File

View File

View File

@ -47,7 +47,7 @@ import os
import getopt import getopt
import glob import glob
import direct import direct
from direct.showutil import Packager from direct.p3d import Packager
from pandac.PandaModules import * from pandac.PandaModules import *
class ArgumentError(StandardError): class ArgumentError(StandardError):

View File

@ -86,8 +86,8 @@ import sys
import getopt import getopt
import os import os
from direct.showutil import Packager from direct.p3d import Packager
from direct.showutil import make_contents from direct.p3d import make_contents
from pandac.PandaModules import * from pandac.PandaModules import *
def usage(code, msg = ''): def usage(code, msg = ''):

68
direct/src/p3d/runp3d.py Normal file
View File

@ -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()

View File

@ -27,6 +27,11 @@ enum NodeType {
NT_text, 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 // Function: write_xml_node
// Description: Recursively writes a node and all of its children to // 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(); const string &value = xnode->ValueStr();
size_t value_length = value.length(); 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_length, sizeof(value_length));
out.write((char *)&value_proof, sizeof(value_proof));
out.write(value.data(), value_length); out.write(value.data(), value_length);
if (type == NT_element) { if (type == NT_element) {
@ -104,7 +117,8 @@ write_xml_node(ostream &out, TiXmlNode *xnode) {
// return value. Returns NULL on error. // return value. Returns NULL on error.
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
static TiXmlNode * 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(); NodeType type = (NodeType)in.get();
if (type == NT_unknown) { if (type == NT_unknown) {
return NULL; return NULL;
@ -115,6 +129,34 @@ read_xml_node(istream &in, char *&buffer, size_t &buffer_length) {
if (in.gcount() != sizeof(value_length)) { if (in.gcount() != sizeof(value_length)) {
return NULL; 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) { if (value_length > buffer_length) {
delete[] buffer; delete[] buffer;
@ -187,7 +229,7 @@ read_xml_node(istream &in, char *&buffer, size_t &buffer_length) {
while (got_child && in && !in.eof()) { while (got_child && in && !in.eof()) {
// We have a child. // 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) { if (xchild != NULL) {
xnode->LinkEndChild(xchild); xnode->LinkEndChild(xchild);
} }
@ -251,7 +293,7 @@ read_xml(istream &in, ostream &logfile) {
// binary read. // binary read.
size_t buffer_length = 128; size_t buffer_length = 128;
char *buffer = new char[buffer_length]; 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; delete[] buffer;
if (xnode == NULL) { if (xnode == NULL) {
return NULL; return NULL;

View File

@ -164,7 +164,7 @@ TiXmlElement *P3DFileParams::
make_xml() { make_xml() {
TiXmlElement *xfparams = new TiXmlElement("fparams"); TiXmlElement *xfparams = new TiXmlElement("fparams");
xfparams->SetAttribute("p3d_filename", _p3d_filename.c_str()); xfparams->SetAttribute("p3d_filename", _p3d_filename);
Tokens::const_iterator ti; Tokens::const_iterator ti;
for (ti = _tokens.begin(); ti != _tokens.end(); ++ti) { for (ti = _tokens.begin(); ti != _tokens.end(); ++ti) {

View File

@ -35,7 +35,7 @@ P3DHost(const string &host_url) :
_xcontents = NULL; _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 // Access: Private
// Description: Hashes the host_url into a (mostly) unique directory // Description: Hashes the host_url into a (mostly) unique directory
// string for this particular host. Stores the result // string, which will be the root of the host's install
// in _host_dir. // tree. Stores the result in _host_dir.
//
// This code is duplicated in Python, in
// AppRunner.determineHostDir().
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
void P3DHost:: void P3DHost::
fill_host_dir() { determine_host_dir() {
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr(); P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
_host_dir = inst_mgr->get_root_dir(); _host_dir = inst_mgr->get_root_dir();
_host_dir += "/"; _host_dir += "/";
@ -213,6 +216,9 @@ fill_host_dir() {
// Look for a server name in the URL. Including this string in the // Look for a server name in the URL. Including this string in the
// directory name makes it friendlier for people browsing the // directory name makes it friendlier for people browsing the
// directory. // 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("://"); size_t p = _host_url.find("://");
if (p != string::npos) { if (p != string::npos) {
size_t start = p + 3; size_t start = p + 3;
@ -254,7 +260,7 @@ fill_host_dir() {
// If we successfully got a hostname, we don't really need the // If we successfully got a hostname, we don't really need the
// full hash. We'll keep half of it. // full hash. We'll keep half of it.
keep_hash = hash_size / 2; keep_hash = keep_hash / 2;
} }
MD5_CTX ctx; MD5_CTX ctx;

View File

@ -51,7 +51,7 @@ public:
const string &package_version); const string &package_version);
private: private:
void fill_host_dir(); void determine_host_dir();
static string standardize_filename(const string &filename); static string standardize_filename(const string &filename);
static bool copy_file(const string &from_filename, const string &to_filename); static bool copy_file(const string &from_filename, const string &to_filename);

View File

@ -842,6 +842,9 @@ make_xml() {
TiXmlElement *xinstance = new TiXmlElement("instance"); TiXmlElement *xinstance = new TiXmlElement("instance");
xinstance->SetAttribute("instance_id", _instance_id); 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(); TiXmlElement *xfparams = _fparams.make_xml();
xinstance->LinkEndChild(xfparams); xinstance->LinkEndChild(xfparams);
@ -850,6 +853,12 @@ make_xml() {
xinstance->LinkEndChild(xwparams); 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; return xinstance;
} }

View File

@ -129,6 +129,30 @@ remove_instance(P3DInstance *inst) {
begin_info_download(); 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 // Function: P3DPackage::begin_info_download
// Access: Private // Access: Private

View File

@ -60,6 +60,8 @@ public:
void add_instance(P3DInstance *inst); void add_instance(P3DInstance *inst);
void remove_instance(P3DInstance *inst); void remove_instance(P3DInstance *inst);
TiXmlElement *make_xml();
private: private:
enum DownloadType { enum DownloadType {
DT_contents_file, DT_contents_file,

View File

@ -127,14 +127,14 @@ run_python() {
Py_DECREF(runp3d_frozen); Py_DECREF(runp3d_frozen);
// So now we can import the module itself. // So now we can import the module itself.
PyObject *runp3d = PyImport_ImportModule("direct.showutil.runp3d"); PyObject *app_runner_module = PyImport_ImportModule("direct.p3d.AppRunner");
if (runp3d == NULL) { if (app_runner_module == NULL) {
PyErr_Print(); PyErr_Print();
return false; return false;
} }
// Get the pointers to the objects needed within the module. // 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) { if (app_runner_class == NULL) {
PyErr_Print(); PyErr_Print();
return false; return false;
@ -149,41 +149,41 @@ run_python() {
Py_DECREF(app_runner_class); Py_DECREF(app_runner_class);
// Get the UndefinedObject 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) { if (_undefined_object_class == NULL) {
PyErr_Print(); PyErr_Print();
return false; return false;
} }
// And the "Undefined" instance. // And the "Undefined" instance.
_undefined = PyObject_GetAttrString(runp3d, "Undefined"); _undefined = PyObject_GetAttrString(app_runner_module, "Undefined");
if (_undefined == NULL) { if (_undefined == NULL) {
PyErr_Print(); PyErr_Print();
return false; return false;
} }
// Get the ConcreteStruct class. // 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) { if (_concrete_struct_class == NULL) {
PyErr_Print(); PyErr_Print();
return false; return false;
} }
// Get the BrowserObject class. // 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) { if (_browser_object_class == NULL) {
PyErr_Print(); PyErr_Print();
return false; return false;
} }
// Get the global TaskManager. // Get the global TaskManager.
_taskMgr = PyObject_GetAttrString(runp3d, "taskMgr"); _taskMgr = PyObject_GetAttrString(app_runner_module, "taskMgr");
if (_taskMgr == NULL) { if (_taskMgr == NULL) {
PyErr_Print(); PyErr_Print();
return false; return false;
} }
Py_DECREF(runp3d); Py_DECREF(app_runner_module);
// Construct a Python wrapper around our methods we need to expose to Python. // 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) { start_instance(P3DCInstance *inst, TiXmlElement *xinstance) {
_instances[inst->get_instance_id()] = inst; _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"); TiXmlElement *xfparams = xinstance->FirstChildElement("fparams");
if (xfparams != (TiXmlElement *)NULL) { if (xfparams != (TiXmlElement *)NULL) {
set_p3d_filename(inst, xfparams); set_p3d_filename(inst, xfparams);
@ -937,6 +945,59 @@ terminate_instance(int id) {
terminate_session(); 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 // Function: P3DPythonRun::set_p3d_filename
// Access: Private // Access: Private

View File

@ -87,6 +87,8 @@ private:
void start_instance(P3DCInstance *inst, TiXmlElement *xinstance); void start_instance(P3DCInstance *inst, TiXmlElement *xinstance);
void terminate_instance(int id); 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 set_p3d_filename(P3DCInstance *inst, TiXmlElement *xfparams);
void setup_window(int id, TiXmlElement *xwparams); void setup_window(int id, TiXmlElement *xwparams);
void setup_window(P3DCInstance *inst, TiXmlElement *xwparams); void setup_window(P3DCInstance *inst, TiXmlElement *xwparams);

View File

@ -1,4 +0,0 @@
/packp3d
/packp3d.bat
/runp3d
/runp3d.bat

View File

@ -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()