mirror of
https://github.com/panda3d/panda3d.git
synced 2025-09-30 08:44:19 -04:00
move code to p3d directory for better organization
This commit is contained in:
parent
01f032a0a5
commit
d8859828ef
@ -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
|
||||
|
0
direct/src/p3d/Sources.pp
Normal file
0
direct/src/p3d/Sources.pp
Normal file
0
direct/src/p3d/__init__.py
Normal file
0
direct/src/p3d/__init__.py
Normal file
@ -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):
|
@ -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 = ''):
|
68
direct/src/p3d/runp3d.py
Normal file
68
direct/src/p3d/runp3d.py
Normal 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()
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -60,6 +60,8 @@ public:
|
||||
void add_instance(P3DInstance *inst);
|
||||
void remove_instance(P3DInstance *inst);
|
||||
|
||||
TiXmlElement *make_xml();
|
||||
|
||||
private:
|
||||
enum DownloadType {
|
||||
DT_contents_file,
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
4
direct/src/showutil/.gitignore
vendored
4
direct/src/showutil/.gitignore
vendored
@ -1,4 +0,0 @@
|
||||
/packp3d
|
||||
/packp3d.bat
|
||||
/runp3d
|
||||
/runp3d.bat
|
@ -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()
|
Loading…
x
Reference in New Issue
Block a user