Browse Source

Merge branch 'release/1.9.x'

Conflicts:
	direct/src/p3d/DeploymentTools.py
rdb 10 years ago
parent
commit
3c2f820ad8

+ 3 - 3
README.md

@@ -31,8 +31,8 @@ are included as part of the Windows 7.1 SDK.
 You will also need to have the third-party dependency libraries available for
 the build scripts to use.  These are available from one of these two URLs,
 depending on whether you are on a 32-bit or 64-bit system:
-https://www.panda3d.org/download/panda3d-1.9.0/panda3d-1.9.0-tools-win32.zip
-https://www.panda3d.org/download/panda3d-1.9.0/panda3d-1.9.0-tools-win64.zip
+https://www.panda3d.org/download/panda3d-1.9.1/panda3d-1.9.1-tools-win32.zip
+https://www.panda3d.org/download/panda3d-1.9.1/panda3d-1.9.1-tools-win64.zip
 
 After acquiring these dependencies, you may simply build Panda3D from the
 command prompt using the following command:
@@ -97,7 +97,7 @@ Mac OS X
 --------
 
 On Mac OS X, you will need to download a set of precompiled thirdparty packages in order to
-compile Panda3D, which can be acquired from [here](https://www.panda3d.org/download/panda3d-1.9.0/panda3d-1.9.0-tools-mac.tar.gz).
+compile Panda3D, which can be acquired from [here](https://www.panda3d.org/download/panda3d-1.9.1/panda3d-1.9.1-tools-mac.tar.gz).
 
 After placing the thirdparty directory inside the panda3d source directory,
 you may build Panda3D using a command like the following:

+ 1 - 1
direct/src/distributed/ClockDelta.py

@@ -1,7 +1,7 @@
 # ClockDelta provides the ability to use clock synchronization for
 # distributed objects
 
-from pandac.PandaModules import *
+from panda3d.core import ClockObject
 from direct.directnotify import DirectNotifyGlobal
 from direct.showbase import DirectObject
 import math

+ 4 - 3
direct/src/gui/DirectDialog.py

@@ -94,8 +94,8 @@ class DirectDialog(DirectFrame):
             ('text',              '',            None),
             ('text_align',        TextNode.ALeft,   None),
             ('text_scale',        0.06,          None),
-            ('image',             None,          None),
-            ('relief',            DGG.RAISED,     None),
+            ('image',             DGG.getDefaultDialogGeom(), None),
+            ('relief',            DGG.getDefaultDialogRelief(), None),
             ('borderWidth',       (0.01, 0.01),  None),
             ('buttonTextList',    [],            DGG.INITOPT),
             ('buttonGeomList',    [],            DGG.INITOPT),
@@ -316,7 +316,8 @@ class DirectDialog(DirectFrame):
         # reduce bottom by pad, button height and 2*button pad
         b = min(b - self['midPad'] - bpad[1] - bHeight - bpad[1], b) - pad[1]
         t = t + self['topPad'] + pad[1]
-        self['frameSize'] = (l, r, b, t)
+        if self['frameSize'] is None:
+            self['frameSize'] = (l, r, b, t)
         self['image_scale'] = (r - l, 1, t - b)
         # Center frame about text and buttons
         self['image_pos'] = ((l+r)*0.5, 0.0, (b+t)*0.5)

+ 8 - 4
direct/src/gui/DirectGuiGlobals.py

@@ -14,6 +14,7 @@ defaultFontFunc = TextNode.getDefaultFont
 defaultClickSound = None
 defaultRolloverSound = None
 defaultDialogGeom = None
+defaultDialogRelief = PGFrameStyle.TBevelOut
 drawOrder = 100
 panel = None
 
@@ -132,13 +133,16 @@ def setDefaultFontFunc(newFontFunc):
 
 def getDefaultDialogGeom():
     global defaultDialogGeom
-    if defaultDialogGeom == None:
-        defaultDialogGeom = loader.loadModel('models/gui/dialog_box_gui', okMissing = True)
     return defaultDialogGeom
 
-def setDefaultDialogGeom(newDialogGeom):
-    global defaultDialogGeom
+def getDefaultDialogRelief():
+    global defaultDialogRelief
+    return defaultDialogRelief
+
+def setDefaultDialogGeom(newDialogGeom, relief=None):
+    global defaultDialogGeom, defaultDialogRelief
     defaultDialogGeom = newDialogGeom
+    defaultDialogRelief = relief
 
 def getDefaultDrawOrder():
     return drawOrder

+ 64 - 8
direct/src/p3d/DeploymentTools.py

@@ -5,6 +5,7 @@ to build for as many platforms as possible. """
 __all__ = ["Standalone", "Installer"]
 
 import os, sys, subprocess, tarfile, shutil, time, zipfile, socket, getpass, struct
+import gzip
 from io import BytesIO, TextIOWrapper
 from direct.directnotify.DirectNotifyGlobal import *
 from direct.showbase.AppRunnerGlobal import appRunner
@@ -994,7 +995,7 @@ class Installer:
         if self.licensefile:
             shutil.copyfile(self.licensefile.toOsSpecific(), Filename(output, "Contents/Resources/License.txt").toOsSpecific())
         pkginfo = open(Filename(output, "Contents/PkgInfo").toOsSpecific(), "w")
-        pkginfo.write("pkmkrpkg1")
+        pkginfo.write("pmkrpkg1")
         pkginfo.close()
         pkginfo = open(Filename(output, "Contents/Resources/package_version").toOsSpecific(), "w")
         pkginfo.write("major: 1\nminor: 9")
@@ -1079,18 +1080,18 @@ class Installer:
         plist.write('</plist>\n')
         plist.close()
 
-        if hasattr(tarfile, "PAX_FORMAT"):
-            archive = tarfile.open(Filename(output, "Contents/Archive.pax.gz").toOsSpecific(), "w:gz", format = tarfile.PAX_FORMAT, tarinfo = TarInfoRootOSX)
-        else:
-            archive = tarfile.open(Filename(output, "Contents/Archive.pax.gz").toOsSpecific(), "w:gz", tarinfo = TarInfoRootOSX)
-        archive.add(appfn.toOsSpecific(), appname)
+        # OS X El Capitan no longer accepts .pax archives - it must be a CPIO archive named .pax.
+        archive = gzip.open(Filename(output, "Contents/Archive.pax.gz").toOsSpecific(), 'wb')
+        self.__ino = 0
+        self.__writeCPIO(archive, appfn, appname)
+        archive.write(b"0707070000000000000000000000000000000000010000000000000000000001300000000000TRAILER!!!\0")
         archive.close()
 
         # Put the .pkg into a zipfile
-        archive = Filename(output.getDirname(), "%s %s.zip" % (self.fullname, self.version))
+        zip_fn = Filename(output.getDirname(), "%s %s.pkg.zip" % (self.fullname, self.version))
         dir = Filename(output.getDirname())
         dir.makeAbsolute()
-        zip = zipfile.ZipFile(archive.toOsSpecific(), 'w')
+        zip = zipfile.ZipFile(zip_fn.toOsSpecific(), 'w')
         for root, dirs, files in self.os_walk(output.toOsSpecific()):
             for name in files:
                 file = Filename.fromOsSpecific(os.path.join(root, name))
@@ -1101,6 +1102,61 @@ class Installer:
 
         return output
 
+    def __writeCPIO(self, archive, fn, name):
+        """ Adds the given fn under the given name to the CPIO archive. """
+
+        st = os.lstat(fn.toOsSpecific())
+
+        archive.write(b"070707") # magic
+        archive.write(b"000000") # dev
+
+        # Synthesize an inode number, different for each entry.
+        self.__ino += 1
+        archive.write("%06o" % (self.__ino))
+
+        # Determine based on the type which mode to write.
+        if os.path.islink(fn.toOsSpecific()):
+            archive.write("%06o" % (st.st_mode))
+            target = os.path.readlink(fn.toOsSpecific()).encode('utf-8')
+            size = len(target)
+        elif os.path.isdir(fn.toOsSpecific()):
+            archive.write(b"040755")
+            size = 0
+        elif not fn.getExtension():  # Binary file?
+            archive.write(b"100755")
+            size = st.st_size
+        else:
+            archive.write(b"100644")
+            size = st.st_size
+
+        archive.write("000000") # uid (root)
+        archive.write("000000") # gid (wheel)
+        archive.write("%06o" % (st.st_nlink))
+        archive.write("000000") # rdev
+        archive.write("%011o" % (st.st_mtime))
+        archive.write("%06o" % (len(name) + 1))
+        archive.write("%011o" % (size))
+
+        # Write the filename, plus terminating NUL byte.
+        archive.write(name.encode('utf-8'))
+        archive.write(b"\0")
+
+        # Copy the file data to the archive.
+        if os.path.islink(fn.toOsSpecific()):
+            archive.write(target)
+        elif size:
+            handle = open(fn.toOsSpecific(), 'rb')
+            data = handle.read(1024 * 1024)
+            while data:
+                archive.write(data)
+                data = handle.read(1024 * 1024)
+            handle.close()
+
+        # If this is a directory, recurse.
+        if os.path.isdir(fn.toOsSpecific()):
+            for child in os.listdir(fn.toOsSpecific()):
+                self.__writeCPIO(archive, Filename(fn, child), name + "/" + child)
+
     def buildNSIS(self, output, platform):
         # Check if we have makensis first
         makensis = None

+ 6 - 3
direct/src/p3d/Packager.py

@@ -2427,7 +2427,7 @@ class Packager:
 
         # Binary files that are considered uncompressible, and are
         # copied without compression.
-        self.uncompressibleExtensions = [ 'mp3', 'ogg', 'wav', 'rml', 'rcss', 'otf' ]
+        self.uncompressibleExtensions = [ 'mp3', 'ogg', 'ogv', 'wav', 'rml', 'rcss', 'otf' ]
         # wav files are compressible, but p3openal_audio won't load
         # them compressed.
         # rml, rcss and otf files must be added here because
@@ -3744,8 +3744,7 @@ class Packager:
         self.__recurseDir(dirname, newDir, unprocessed = unprocessed)
 
     def __recurseDir(self, filename, newName, unprocessed = None, packageTree = None):
-        dirList = vfs.scanDirectory(filename)
-        if dirList:
+        if filename.isDirectory():
             # It's a directory name.  Recurse.
             prefix = newName
             if prefix and prefix[-1] != '/':
@@ -3753,6 +3752,7 @@ class Packager:
 
             # First check if this is a Python package tree.  If so, add it
             # implicitly as a module.
+            dirList = vfs.scanDirectory(filename)
             for subfile in dirList:
                 filename = subfile.getFilename()
                 if filename.getBasename() == '__init__.py':
@@ -3764,6 +3764,9 @@ class Packager:
                 self.__recurseDir(filename, prefix + filename.getBasename(),
                                   unprocessed = unprocessed)
             return
+        elif not filename.exists():
+            # It doesn't exist.  Perhaps it's a virtual file.  Ignore it.
+            return
 
         # It's a file name.  Add it.
         ext = filename.getExtension()

+ 0 - 2
direct/src/p3d/panda3d.pdef

@@ -103,8 +103,6 @@ class panda3d(package):
 
     excludeModule('MySQLdb', '_mysql')
 
-    excludeModule('xml', 'xml.parsers.expat', 'xml.sax')
-
     # Most of the core Panda3D DLL's will be included implicitly due to
     # being referenced by the above Python code.  Here we name a few more
     # that are also needed, but aren't referenced by any code.  Again,

+ 92 - 16
doc/ReleaseNotes

@@ -3,31 +3,107 @@
 This minor release fixes some important regressions and bugs found
 in 1.9.0, but also introduces a few minor features.
 
-It also reintroduces the deployment pipeline that was absent from
+It also reintroduces the deployment tools that were absent from
 the previous release.
 
-* Textures were not being scaled to power-of-2 in some cases
-* Fix various issues with shader inputs
-* Bullet step function accidentally defaulted to step size of 0
-* Use model-path for finding libRocket assets
-* Fix inconsistent behavior with non-power-of-2 textures in rocket
-* Fix regression with memoryviews
-* Fix symbol error when loading libp3ffmpeg on Mac OS X
+The following issues were fixed:
+* SDK now properly installs in Mac OS X 10.11 "El Capitan"
+* Windows 8.1+ no longer applies DPI virtualization to Panda window
+* Fix ffmpeg library load issue on Mac OS X
 * Fix issues running maya2egg on Mac OS X
-* PStats now tracks memory residency of graphics buffers
-* Support wireframe and point rendering modes in OpenGL ES
-* Add missing keys to libRocket keymap
+* Fix compiler errors on different platforms
+* Fix various rare crashes
+* Fix crashes on shutdown in threaded pipeline
+* Fix low-level threading crash on ARM machines
+* More reliably and robustly handle failures opening OpenAL device
+* Textures were not being scaled to power-of-2 in some cases
+* Correct scaling of normal vectors with flatten operation
+* Correct positioning of viewing axis when showing lens frustum
+* Add dpi-window-resize option to auto-resize window on DPI change
+* Fix assertions when alpha-file-channel references unknown channel
+* Use OpenGL-style vertex colors by default on non-Windows systems
+* Default vertex column alignment is now 4 bytes
+* Add PNMImage premultiply/unpremultiply methods.
 * Fix incorrect parsing of numbers with exponents in Config.prc
-* Various performance optimizations
 * Fix for reading URLs mounted via the virtual file system
+* Fix shader generator memory leaks and runtime performance
+* Fix shader generator scaling of binormals and tangents
+* Expose _NET_WM_PID to window managers in X11
+* Fix a range of bugs in tinydisplay renderer.
+* Don't error when setting lens far distance to infinity
+* Allow passing custom lens to saveCubeMap/saveSphereMap
+* Fix errors in saveCubeMap/saveSphereMap in threaded pipeline
+* Fix DynamicTextFont.makeCopy()
+* Make Texture memory size estimation more accurate
+* Fix various window resizing issues
+* Fix PandaSystem.getCompiler() value for clang (it reported gcc)
+* x2egg no longer replaces face normals with vertex normals
+* Include Eigen headers in Mac and Windows SDK
+* Added geomipterrain-incorrect-normals setting, default=true
+* DisplayInformation resolution list was missing on Windows
+* Upgrade FMOD and Bullet versions on Windows and Mac OS X
+* Various performance optimizations
+* Fixed various other bugs not listed here.
+
+Fixes and improvements for the runtime:
+* Fix splash screen freezing in the X11 web plug-in
+* pdeploy will now handle extracted files (eg. .ico and .cur)
+* Added more options for customizing splash screen
+* Fix missing xml and ast modules from morepy package
+* Certificate dialog is now localized to various languages
+* Fix packp3d error when Python file is not in a package
+* Pass on failing exit status from packaged application
+* Remove annoying ":Packager(warning): No such file" warning
+* Fix issue installing pdeploy-generated .pkg on OS X 10.11
+
+Fixes for the Python API:
+* Fix mysterious and rare crash in tp_traverse
+* Bullet step function accidentally defaulted to step size of 0
+* Fix overflow of file offsets (eg. when seeking in huge files)
+* Fix regression with memoryviews
+* Fix hasattr/getattr of vector classes for invalid attributes
+* Allow passing a long to methods accepting an int
+* Fix crash when passing None to Filename constructor
+* MouseWatcherGroup was erroneously not exposed in 1.9.0
+* ShowBase no longer unmounts VFS when shutting down
+* No longer requires setting PATH to import panda3d.*
+* DirectDialog default geom is once again respected
+* DirectDialog no longer overrides custom frameSize
+* Fix WebcamVideo/MicrophoneAudio.getOptions() methods
+
+Changes relating to the OpenGL renderer:
+* Various performance improvements
+* Fix point/line thickness setting
 * Improve GLSL error reporting
+* Fix Intel driver issues, particularly with geometry shaders
+* Add more error checking for parameter types
+* Integer shader inputs were not being converted to float properly
+* Fix crash passing an undersized array to a GLSL shader input
+* p3d_ColorScale et al may now be declared as vec3
+* Fix flickering when using trans_model_to_apiview in Cg
+* Support wireframe and point rendering modes in OpenGL ES
 * Fix issue with model disappearing in rare cases with GLSL
-* Fix shader generator memory leaks and runtime performance
+* Fix ColorWriteAttrib not working as it should
+* Allow deactivating PStats collectors for GPU timers
+* Memory residency of graphics buffers now tracked by PStats
+* Allow changing OpenGL coordinate system with gl-coordinate-system
+
+Fixes for libRocket integration:
+* libRocket did not work on Mac OS X in 1.9.0
+* Fix inconsistent behavior with non-power-of-2 textures in rocket
+* Use model-path for finding libRocket assets
+* Add missing keys to libRocket keymap
+* libRocket elements showed up white in tinydisplay
+
+New features:
+* Add -L (lighting) and -P (graphics pipe) pview options
 * Add M_confined mouse mode that keeps cursor in window
-* Expose _NET_WM_PID to window managers in X11
-* bam2egg supports collision sphere and plane solids
 * Add sample program demonstrating mouse modes
-* Add -L (lighting) and -P (graphics pipe) pview options
+* bam2egg supports collision sphere and plane solids
+* p3d_TransformTable GLSL input backported from 1.10 branch
+* Add openal-device setting for selecting OpenAL audio output
+* Add limited modification timestamp tracking for Ramdisk mounts
+* Support for Autodesk Maya 2016
 
 ------------------------  RELEASE 1.9.0  ------------------------
 

+ 134 - 76
makepanda/makepanda.py

@@ -327,7 +327,7 @@ def parseopts(args):
     else:
         PkgDisable("TOUCHINPUT")
 
-    if clean_build:
+    if clean_build and os.path.isdir(GetOutputDir()):
         print("Deleting %s" % (GetOutputDir()))
         shutil.rmtree(GetOutputDir())
 
@@ -783,9 +783,7 @@ if (COMPILER=="GCC"):
         rocket_libs = ("RocketCore", "RocketControls")
         if (GetOptimize() <= 3):
             rocket_libs += ("RocketDebugger",)
-        if (GetHost() != "darwin"):
-            # We use a statically linked libboost_python on OSX
-            rocket_libs += ("boost_python",)
+        rocket_libs += ("boost_python",)
         SmartPkgEnable("ROCKET",    "",          rocket_libs, "Rocket/Core.h")
 
         if not PkgSkip("PYTHON"):
@@ -2762,9 +2760,15 @@ if tp_dir is not None:
     dylibs = set()
 
     if GetTarget() == 'darwin':
+        # Make a list of all the dylibs we ship, to figure out whether we should use
+        # install_name_tool to correct the library reference to point to our copy.
         for lib in glob.glob(tp_dir + "/*/lib/*.dylib"):
             dylibs.add(os.path.basename(lib))
 
+        if not PkgSkip("PYTHON"):
+            for lib in glob.glob(tp_dir + "/*/lib/" + SDK["PYTHONVERSION"] + "/*.dylib"):
+                dylibs.add(os.path.basename(lib))
+
     for pkg in PkgListGet():
         if PkgSkip(pkg):
             continue
@@ -2775,48 +2779,68 @@ if tp_dir is not None:
                 CopyAllFiles(GetOutputDir() + "/bin/", tp_pkg + "/bin/")
                 if (PkgSkip("PYTHON")==0 and os.path.exists(tp_pkg + "/bin/" + SDK["PYTHONVERSION"])):
                     CopyAllFiles(GetOutputDir() + "/bin/", tp_pkg + "/bin/" + SDK["PYTHONVERSION"] + "/")
-        else:
-            for tp_lib in glob.glob(tp_pkg + "/lib/*.so*"):
-                CopyFile(GetOutputDir() + "/lib/" + os.path.basename(tp_lib), tp_lib)
-
-            if not PkgSkip("PYTHON"):
-                for tp_lib in glob.glob(os.path.join(tp_pkg, "lib", SDK["PYTHONVERSION"], "*.so*")):
-                    CopyFile(GetOutputDir() + "/lib/" + os.path.basename(tp_lib), tp_lib)
-
-            if GetTarget() == 'darwin':
-                tp_libs = glob.glob(tp_pkg + "/lib/*.dylib")
 
-                if not PkgSkip("PYTHON"):
-                    tp_libs += glob.glob(os.path.join(tp_pkg, "lib", SDK["PYTHONVERSION"], "*.dylib"))
+        elif GetTarget() == 'darwin':
+            tp_libs = glob.glob(tp_pkg + "/lib/*.dylib")
 
-                for tp_lib in tp_libs:
-                    basename = os.path.basename(tp_lib)
+            if not PkgSkip("PYTHON"):
+                tp_libs += glob.glob(os.path.join(tp_pkg, "lib", SDK["PYTHONVERSION"], "*.dylib"))
+                tp_libs += glob.glob(os.path.join(tp_pkg, "lib", SDK["PYTHONVERSION"], "*.so"))
+                if pkg != 'PYTHON':
+                    tp_libs += glob.glob(os.path.join(tp_pkg, "lib", SDK["PYTHONVERSION"], "*.py"))
+
+            for tp_lib in tp_libs:
+                basename = os.path.basename(tp_lib)
+                if basename.endswith('.dylib'):
+                    # It's a dynamic link library.  Put it in the lib directory.
                     target = GetOutputDir() + "/lib/" + basename
-                    if not NeedsBuild([target], [tp_lib]):
-                        continue
-
-                    CopyFile(target, tp_lib)
-                    if os.path.islink(target):
+                    dep_prefix = "@loader_path/../lib/"
+                    lib_id = dep_prefix + basename
+                else:
+                    # It's a Python module, like _rocketcore.so.  Copy it to the root, because
+                    # nowadays the 'lib' directory may no longer be on the PYTHONPATH.
+                    target = GetOutputDir() + "/" + basename
+                    dep_prefix = "@loader_path/lib/"
+                    lib_id = basename
+
+                if not NeedsBuild([target], [tp_lib]):
+                    continue
+
+                CopyFile(target, tp_lib)
+                if os.path.islink(target) or target.endswith('.py'):
+                    continue
+
+                # Correct the inter-library dependencies so that the build is relocatable.
+                oscmd('install_name_tool -id %s %s' % (lib_id, target))
+                oscmd("otool -L %s | grep .dylib > %s/tmp/otool-libs.txt" % (target, GetOutputDir()), True)
+
+                for line in open(GetOutputDir() + "/tmp/otool-libs.txt", "r"):
+                    line = line.strip()
+                    if not line or line.startswith(dep_prefix) or line.endswith(":"):
                         continue
 
-                    # Correct the inter-library dependencies so that the build is relocatable.
-                    oscmd('install_name_tool -id @loader_path/../lib/%s %s' % (basename, target))
-                    oscmd("otool -L %s | grep .dylib > %s/tmp/otool-libs.txt" % (target, GetOutputDir()), True)
+                    libdep = line.split(" ", 1)[0]
+                    dep_basename = os.path.basename(libdep)
+                    if dep_basename in dylibs:
+                        oscmd("install_name_tool -change %s %s%s %s" % (libdep, dep_prefix, dep_basename, target), True)
 
-                    for line in open(GetOutputDir() + "/tmp/otool-libs.txt", "r"):
-                        line = line.strip()
-                        if not line or line.startswith('@loader_path/../lib/') or line.endswith(":"):
-                            continue
+                JustBuilt([target], [tp_lib])
 
-                        libdep = line.split(" ", 1)[0]
-                        dep_basename = os.path.basename(libdep)
-                        if dep_basename in dylibs:
-                            oscmd("install_name_tool -change %s @loader_path/../lib/%s %s" % (libdep, dep_basename, target), True)
+            for fwx in glob.glob(tp_pkg + "/*.framework"):
+                CopyTree(GetOutputDir() + "/Frameworks/" + os.path.basename(fwx), fwx)
 
-                    JustBuilt([target], [tp_lib])
+        else:  # Linux / FreeBSD case.
+            for tp_lib in glob.glob(tp_pkg + "/lib/*.so*"):
+                CopyFile(GetOutputDir() + "/lib/" + os.path.basename(tp_lib), tp_lib)
 
-                for fwx in glob.glob(tp_pkg + "/*.framework"):
-                    CopyTree(GetOutputDir() + "/Frameworks/" + os.path.basename(fwx), fwx)
+            if not PkgSkip("PYTHON"):
+                for tp_lib in glob.glob(os.path.join(tp_pkg, "lib", SDK["PYTHONVERSION"], "*.so*")):
+                    base = os.path.basename(tp_lib)
+                    if base.startswith('lib'):
+                        CopyFile(GetOutputDir() + "/lib/" + base, tp_lib)
+                    else:
+                        # It's a Python module, like _rocketcore.so.
+                        CopyFile(GetOutputDir() + "/" + base, tp_lib)
 
     if GetTarget() == 'windows':
         CopyAllFiles(GetOutputDir() + "/bin/", tp_dir + "extras/bin/")
@@ -2853,8 +2877,8 @@ if (PkgSkip("PYTHON")==0 and os.path.isdir(GetThirdpartyBase()+"/Pmw")):
 ConditionalWriteFile(GetOutputDir()+'/include/ctl3d.h', '/* dummy file to make MAX happy */')
 
 # Since Eigen is included by all sorts of core headers, as a convenience
-# to C++ users on Windows, we include it in the Panda include directory.
-if not PkgSkip("EIGEN") and GetTarget() == "windows" and GetThirdpartyDir():
+# to C++ users on Win and Mac, we include it in the Panda include directory.
+if not PkgSkip("EIGEN") and GetTarget() in ("windows", "darwin") and GetThirdpartyDir():
     CopyTree(GetOutputDir()+'/include/Eigen', GetThirdpartyDir()+'eigen/include/Eigen')
 
 ########################################################################
@@ -6799,7 +6823,6 @@ def MakeInstallerOSX():
     if (os.path.exists("Panda3D-rw.dmg")): oscmd('rm -f Panda3D-rw.dmg')
 
     oscmd("mkdir -p dstroot/base/Developer/Panda3D/lib")
-    oscmd("mkdir -p dstroot/base/Developer/Panda3D/panda3d")
     oscmd("mkdir -p dstroot/base/Developer/Panda3D/etc")
     oscmd("cp %s/etc/Config.prc           dstroot/base/Developer/Panda3D/etc/Config.prc" % GetOutputDir())
     oscmd("cp %s/etc/Confauto.prc         dstroot/base/Developer/Panda3D/etc/Confauto.prc" % GetOutputDir())
@@ -6810,43 +6833,42 @@ def MakeInstallerOSX():
     if os.path.isdir(GetOutputDir()+"/plugins"):
         oscmd("cp -R %s/plugins           dstroot/base/Developer/Panda3D/plugins" % GetOutputDir())
 
-    install_libs = []
+    # Libraries that shouldn't be in base, but are instead in other modules.
+    no_base_libs = ['libp3ffmpeg', 'libp3fmod_audio', 'libfmodex', 'libfmodexL']
+
     for base in os.listdir(GetOutputDir()+"/lib"):
-        if (not base.endswith(".a")):
-            install_libs.append("lib/"+base)
-    for base in os.listdir(GetOutputDir()+"/panda3d"):
-        if (not base.endswith(".a")):
-            install_libs.append("panda3d/"+base)
-
-    for base in install_libs:
-        libname = "dstroot/base/Developer/Panda3D/" + base
-        # We really need to specify -R in order not to follow symlinks
-        # On OSX, just specifying -P is not enough to do that.
-        oscmd("cp -R -P " + GetOutputDir() + "/" + base + " " + libname)
-
-    oscmd("mkdir -p dstroot/tools/Developer/Tools/Panda3D")
-    oscmd("mkdir -p dstroot/tools/Developer/Panda3D")
+        if not base.endswith(".a") and base.split('.')[0] not in no_base_libs:
+            libname = "dstroot/base/Developer/Panda3D/lib/" + base
+            # We really need to specify -R in order not to follow symlinks
+            # On OSX, just specifying -P is not enough to do that.
+            oscmd("cp -R -P " + GetOutputDir() + "/lib/" + base + " " + libname)
+
+    oscmd("mkdir -p dstroot/tools/Developer/Panda3D/bin")
+    oscmd("mkdir -p dstroot/tools/Developer/Tools")
+    oscmd("ln -s ../Panda3D/bin dstroot/tools/Developer/Tools/Panda3D")
     oscmd("mkdir -p dstroot/tools/etc/paths.d")
     # Trailing newline is important, works around a bug in OSX
-    WriteFile("dstroot/tools/etc/paths.d/Panda3D", "/Developer/Tools/Panda3D\n")
+    WriteFile("dstroot/tools/etc/paths.d/Panda3D", "/Developer/Panda3D/bin\n")
 
     oscmd("mkdir -p dstroot/tools/usr/local/share/man/man1")
     oscmd("cp doc/man/*.1 dstroot/tools/usr/local/share/man/man1/")
 
     for base in os.listdir(GetOutputDir()+"/bin"):
-        binname = "dstroot/tools/Developer/Tools/Panda3D/" + base
+        binname = "dstroot/tools/Developer/Panda3D/bin/" + base
         # OSX needs the -R argument to copy symbolic links correctly, it doesn't have -d. How weird.
         oscmd("cp -R " + GetOutputDir() + "/bin/" + base + " " + binname)
 
     if PkgSkip("PYTHON")==0:
         PV = SDK["PYTHONVERSION"].replace("python", "")
         oscmd("mkdir -p dstroot/pythoncode/usr/local/bin")
-        oscmd("mkdir -p dstroot/pythoncode/Developer/Panda3D")
+        oscmd("mkdir -p dstroot/pythoncode/Developer/Panda3D/panda3d")
         oscmd("mkdir -p dstroot/pythoncode/Library/Python/%s/site-packages" % PV)
         WriteFile("dstroot/pythoncode/Library/Python/%s/site-packages/Panda3D.pth" % PV, "/Developer/Panda3D")
         oscmd("cp -R %s/pandac                dstroot/pythoncode/Developer/Panda3D/pandac" % GetOutputDir())
         oscmd("cp -R %s/direct                dstroot/pythoncode/Developer/Panda3D/direct" % GetOutputDir())
         oscmd("ln -s %s                       dstroot/pythoncode/usr/local/bin/ppython" % SDK["PYTHONEXEC"])
+        oscmd("cp -R %s/*.so                  dstroot/pythoncode/Developer/Panda3D/" % GetOutputDir())
+        oscmd("cp -R %s/*.py                  dstroot/pythoncode/Developer/Panda3D/" % GetOutputDir())
         if os.path.isdir(GetOutputDir()+"/Pmw"):
             oscmd("cp -R %s/Pmw               dstroot/pythoncode/Developer/Panda3D/Pmw" % GetOutputDir())
             compileall.compile_dir("dstroot/pythoncode/Developer/Panda3D/Pmw")
@@ -6855,6 +6877,26 @@ def MakeInstallerOSX():
             if ((base != "extensions") and (base != "extensions_native")):
                 compileall.compile_dir("dstroot/pythoncode/Developer/Panda3D/direct/"+base)
 
+        for base in os.listdir(GetOutputDir()+"/panda3d"):
+            if base.endswith('.py') or base.endswith('.so'):
+                libname = "dstroot/pythoncode/Developer/Panda3D/panda3d/" + base
+                # We really need to specify -R in order not to follow symlinks
+                # On OSX, just specifying -P is not enough to do that.
+                oscmd("cp -R -P " + GetOutputDir() + "/panda3d/" + base + " " + libname)
+
+    if not PkgSkip("FFMPEG"):
+        oscmd("mkdir -p dstroot/ffmpeg/Developer/Panda3D/lib")
+        oscmd("cp -R %s/lib/libp3ffmpeg.* dstroot/ffmpeg/Developer/Panda3D/lib/" % GetOutputDir())
+
+    #if not PkgSkip("OPENAL"):
+    #    oscmd("mkdir -p dstroot/openal/Developer/Panda3D/lib")
+    #    oscmd("cp -R %s/lib/libp3openal_audio.* dstroot/openal/Developer/Panda3D/lib/" % GetOutputDir())
+
+    if not PkgSkip("FMODEX"):
+        oscmd("mkdir -p dstroot/fmodex/Developer/Panda3D/lib")
+        oscmd("cp -R %s/lib/libp3fmod_audio.* dstroot/fmodex/Developer/Panda3D/lib/" % GetOutputDir())
+        oscmd("cp -R %s/lib/libfmodex* dstroot/fmodex/Developer/Panda3D/lib/" % GetOutputDir())
+
     oscmd("mkdir -p dstroot/headers/Developer/Panda3D")
     oscmd("cp -R %s/include               dstroot/headers/Developer/Panda3D/include" % GetOutputDir())
 
@@ -6873,7 +6915,10 @@ def MakeInstallerOSX():
     oscmd("mkdir -p dstroot/Panda3D/Panda3D.mpkg/Contents/Resources/en.lproj/")
 
     pkgs = ["base", "tools", "headers"]
-    if PkgSkip("PYTHON")==0:     pkgs.append("pythoncode")
+    if not PkgSkip("PYTHON"):    pkgs.append("pythoncode")
+    if not PkgSkip("FFMPEG"):    pkgs.append("ffmpeg")
+    #if not PkgSkip("OPENAL"):    pkgs.append("openal")
+    if not PkgSkip("FMODEX"):    pkgs.append("fmodex")
     if os.path.isdir("samples"): pkgs.append("samples")
     for pkg in pkgs:
         identifier = "org.panda3d.panda3d.%s.pkg" % pkg
@@ -6918,38 +6963,51 @@ def MakeInstallerOSX():
     dist.write('    <options customize="always" allow-external-scripts="no" rootVolumeOnly="false"/>\n')
     dist.write('    <license language="en" mime-type="text/plain">%s</license>\n' % ReadFile("doc/LICENSE"))
     dist.write('    <choices-outline>\n')
-    dist.write('        <line choice="base"/>\n')
-    dist.write('        <line choice="tools"/>\n')
-    if PkgSkip("PYTHON")==0:
-        dist.write('        <line choice="pythoncode"/>\n')
-    if os.path.isdir("samples"):
-        dist.write('        <line choice="samples"/>\n')
-    dist.write('        <line choice="headers"/>\n')
+    for pkg in pkgs:
+        dist.write('        <line choice="%s"/>\n' % (pkg))
     dist.write('    </choices-outline>\n')
     dist.write('    <choice id="base" title="Panda3D Base Installation" description="This package contains the Panda3D libraries, configuration files and models/textures that are needed to use Panda3D. Location: /Developer/Panda3D/" start_enabled="false">\n')
     dist.write('        <pkg-ref id="org.panda3d.panda3d.base.pkg"/>\n')
     dist.write('    </choice>\n')
-    dist.write('    <choice id="tools" title="Tools" tooltip="Useful tools and model converters to help with Panda3D development" description="This package contains the various utilities that ship with Panda3D, including packaging tools, model converters, and many more. Location: /Developer/Tools/Panda3D/">\n')
+    dist.write('    <choice id="tools" title="Tools" tooltip="Useful tools and model converters to help with Panda3D development" description="This package contains the various utilities that ship with Panda3D, including packaging tools, model converters, and many more. Location: /Developer/Panda3D/bin/">\n')
     dist.write('        <pkg-ref id="org.panda3d.panda3d.tools.pkg"/>\n')
     dist.write('    </choice>\n')
-    if PkgSkip("PYTHON")==0:
-        dist.write('    <choice id="pythoncode" title="Python Code" tooltip="Code you\'ll need for Python development" description="This package contains the \'direct\', \'pandac\' and \'panda3d\' python packages that are needed to do Python development with Panda3D. Location: /Developer/Panda3D/">\n')
+
+    if not PkgSkip("PYTHON"):
+        dist.write('    <choice id="pythoncode" title="Python Support" tooltip="Python bindings for the Panda3D libraries" description="This package contains the \'direct\', \'pandac\' and \'panda3d\' python packages that are needed to do Python development with Panda3D. Location: /Developer/Panda3D/">\n')
         dist.write('        <pkg-ref id="org.panda3d.panda3d.pythoncode.pkg"/>\n')
         dist.write('    </choice>\n')
+
+    if not PkgSkip("FFMPEG"):
+        dist.write('    <choice id="ffmpeg" title="FFMpeg Plug-In" tooltip="FFMpeg video and audio decoding plug-in" description="This package contains the FFMpeg plug-in, which is used for decoding video and audio files with OpenAL.')
+        if PkgSkip("VORBIS"):
+            dist.write('  It is not required for loading .wav files, which Panda3D can read out of the box.">\n')
+        else:
+            dist.write('  It is not required for loading .wav or .ogg files, which Panda3D can read out of the box.">\n')
+        dist.write('        <pkg-ref id="org.panda3d.panda3d.ffmpeg.pkg"/>\n')
+        dist.write('    </choice>\n')
+
+    #if not PkgSkip("OPENAL"):
+    #    dist.write('    <choice id="openal" title="OpenAL Audio Plug-In" tooltip="OpenAL audio output plug-in" description="This package contains the OpenAL audio plug-in, which is an open-source library for playing sounds.">\n')
+    #    dist.write('        <pkg-ref id="org.panda3d.panda3d.openal.pkg"/>\n')
+    #    dist.write('    </choice>\n')
+
+    if not PkgSkip("FMODEX"):
+        dist.write('    <choice id="fmodex" title="FMOD Ex Plug-In" tooltip="FMOD Ex audio output plug-in" description="This package contains the FMOD Ex audio plug-in, which is a commercial library for playing sounds.  It is an optional component as Panda3D can use the open-source alternative OpenAL instead.">\n')
+        dist.write('        <pkg-ref id="org.panda3d.panda3d.fmodex.pkg"/>\n')
+        dist.write('    </choice>\n')
+
     if os.path.isdir("samples"):
         dist.write('    <choice id="samples" title="Sample Programs" tooltip="Python sample programs that use Panda3D" description="This package contains the Python sample programs that can help you with learning how to use Panda3D. Location: /Developer/Examples/Panda3D/">\n')
         dist.write('        <pkg-ref id="org.panda3d.panda3d.samples.pkg"/>\n')
         dist.write('    </choice>\n')
+
     dist.write('    <choice id="headers" title="C++ Header Files" tooltip="Header files for C++ development with Panda3D" description="This package contains the C++ header files that are needed in order to do C++ development with Panda3D. You don\'t need this if you want to develop in Python. Location: /Developer/Panda3D/include/" start_selected="false">\n')
     dist.write('        <pkg-ref id="org.panda3d.panda3d.headers.pkg"/>\n')
     dist.write('    </choice>\n')
-    dist.write('    <pkg-ref id="org.panda3d.panda3d.base.pkg" installKBytes="%d" version="1" auth="Root">file:./Contents/Packages/base.pkg</pkg-ref>\n' % (GetDirectorySize("dstroot/base") // 1024))
-    dist.write('    <pkg-ref id="org.panda3d.panda3d.tools.pkg" installKBytes="%d" version="1" auth="Root">file:./Contents/Packages/tools.pkg</pkg-ref>\n' % (GetDirectorySize("dstroot/tools") // 1024))
-    if PkgSkip("PYTHON")==0:
-        dist.write('    <pkg-ref id="org.panda3d.panda3d.pythoncode.pkg" installKBytes="%d" version="1" auth="Root">file:./Contents/Packages/pythoncode.pkg</pkg-ref>\n' % (GetDirectorySize("dstroot/pythoncode") // 1024))
-    if os.path.isdir("samples"):
-        dist.write('    <pkg-ref id="org.panda3d.panda3d.samples.pkg" installKBytes="%d" version="1" auth="Root">file:./Contents/Packages/samples.pkg</pkg-ref>\n' % (GetDirectorySize("dstroot/samples") // 1024))
-    dist.write('    <pkg-ref id="org.panda3d.panda3d.headers.pkg" installKBytes="%d" version="1" auth="Root">file:./Contents/Packages/headers.pkg</pkg-ref>\n' % (GetDirectorySize("dstroot/headers") // 1024))
+    for pkg in pkgs:
+        size = GetDirectorySize("dstroot/" + pkg) // 1024
+        dist.write('    <pkg-ref id="org.panda3d.panda3d.%s.pkg" installKBytes="%d" version="1" auth="Root">file:./Contents/Packages/%s.pkg</pkg-ref>\n' % (pkg, size, pkg))
     dist.write('</installer-script>\n')
     dist.close()
 

+ 27 - 6
makepanda/makepandacore.py

@@ -2546,6 +2546,10 @@ def SetupBuildEnvironment(compiler):
             dyldpath.insert(0, os.path.join(builtdir, 'lib'))
             os.environ["DYLD_LIBRARY_PATH"] = os.pathsep.join(dyldpath)
 
+            # OS X 10.11 removed DYLD_LIBRARY_PATH, but we still need to pass
+            # on our lib directory to ppackage, so add it to PATH instead.
+            os.environ["PATH"] = os.path.join(builtdir, 'lib') + ':' + os.environ.get("PATH", "")
+
         # Workaround around compile issue on PCBSD
         if (os.path.exists("/usr/PCBSD")):
             os.environ["LD_LIBRARY_PATH"] += os.pathsep + "/usr/PCBSD/local/lib"
@@ -2563,8 +2567,12 @@ def CopyFile(dstfile, srcfile):
     if NeedsBuild([dstfile], [srcfile]):
         if os.path.islink(srcfile):
             # Preserve symlinks
-            if os.path.exists(dstfile):
+            if os.path.isfile(dstfile) or os.path.islink(dstfile):
+                print("Removing file %s" % (dstfile))
                 os.unlink(dstfile)
+            elif os.path.isdir(dstfile):
+                print("Removing directory %s" % (dstfile))
+                shutil.rmtree(dstfile)
             os.symlink(os.readlink(srcfile), dstfile)
         else:
             WriteBinaryFile(dstfile, ReadBinaryFile(srcfile))
@@ -2595,24 +2603,37 @@ def CopyAllJavaSources(dir, skip=[]):
             JustBuilt([dstfile], [srcfile])
 
 def CopyTree(dstdir, srcdir, omitVCS=True):
-    if (os.path.isdir(dstdir)):
-        for entry in os.listdir(srcdir):
+    if os.path.isdir(dstdir):
+        source_entries = os.listdir(srcdir)
+        for entry in source_entries:
             srcpth = os.path.join(srcdir, entry)
             dstpth = os.path.join(dstdir, entry)
-            if (os.path.isfile(srcpth)):
+
+            if os.path.islink(srcpth) or os.path.isfile(srcpth):
                 if not omitVCS or entry not in VCS_FILES:
                     CopyFile(dstpth, srcpth)
             else:
                 if not omitVCS or entry not in VCS_DIRS:
                     CopyTree(dstpth, srcpth)
+
+        # Delete files in dstdir that are not in srcdir.
+        for entry in os.listdir(dstdir):
+            if entry not in source_entries:
+                path = os.path.join(dstdir, entry)
+                if os.path.islink(path) or os.path.isfile(path):
+                    os.remove(path)
+                elif os.path.isdir(path):
+                    shutil.rmtree(path)
     else:
         if GetHost() == 'windows':
             srcdir = srcdir.replace('/', '\\')
             dstdir = dstdir.replace('/', '\\')
             cmd = 'xcopy /I/Y/E/Q "' + srcdir + '" "' + dstdir + '"'
+            oscmd(cmd)
         else:
-            cmd = 'cp -R -f ' + srcdir + ' ' + dstdir
-        oscmd(cmd)
+            if subprocess.call(['cp', '-R', '-f', srcdir, dstdir]) != 0:
+                exit("Copy failed.")
+
         if omitVCS:
             DeleteVCS(dstdir)
 

+ 6 - 0
panda/src/audiotraits/config_openalAudio.cxx

@@ -27,6 +27,12 @@ ConfigureFn(config_openalAudio) {
   init_libOpenALAudio();
 }
 
+ConfigVariableString openal_device
+("openal-device", "",
+ PRC_DESC("Specify the OpenAL device string for audio playback (no quotes).  If this "
+          "is not specified, the OpenAL default device is used."));
+
+
 ////////////////////////////////////////////////////////////////////
 //     Function: init_libOpenALAudio
 //  Description: Initializes the library.  This must be called at

+ 2 - 0
panda/src/audiotraits/config_openalAudio.h

@@ -27,4 +27,6 @@ NotifyCategoryDecl(openalAudio, EXPCL_OPENAL_AUDIO, EXPTP_OPENAL_AUDIO);
 extern EXPCL_OPENAL_AUDIO void init_libOpenALAudio();
 extern "C" EXPCL_OPENAL_AUDIO Create_AudioManager_proc *get_audio_manager_func_openal_audio();
 
+extern ConfigVariableString openal_device;
+
 #endif // CONFIG_OPENALAUDIO_H

+ 125 - 10
panda/src/audiotraits/openalAudioManager.cxx

@@ -19,6 +19,7 @@
 #include "config_audio.h"
 #include "config_util.h"
 #include "config_express.h"
+#include "config_openalAudio.h"
 #include "openalAudioManager.h"
 #include "openalAudioSound.h"
 #include "virtualFileSystem.h"
@@ -27,6 +28,14 @@
 
 #include <algorithm>
 
+#ifndef ALC_DEFAULT_ALL_DEVICES_SPECIFIER
+#define ALC_DEFAULT_ALL_DEVICES_SPECIFIER 0x1012
+#endif
+
+#ifndef ALC_ALL_DEVICES_SPECIFIER
+#define ALC_ALL_DEVICES_SPECIFIER 0x1013
+#endif
+
 TypeHandle OpenALAudioManager::_type_handle;
 
 ReMutex OpenALAudioManager::_lock;
@@ -90,7 +99,7 @@ OpenALAudioManager() {
   _active = audio_active;
   _volume = audio_volume;
   _play_rate = 1.0f;
-  
+
   _cache_limit = audio_cache_limit;
 
   _concurrent_sound_limit = 0;
@@ -116,20 +125,50 @@ OpenALAudioManager() {
   _forward_up[5] = 0;
 
   // Initialization
+  audio_cat.init();
   if (_active_managers == 0 || !_openal_active) {
-    _device = alcOpenDevice(NULL); // select the "preferred device"
-    if (!_device) {
-      // this is a unique kind of error
-      audio_error("OpenALAudioManager: alcOpenDevice(NULL): ALC couldn't open device");
+    _device = NULL;
+    string dev_name = select_audio_device();
+
+    if (!dev_name.empty()) {
+      // Open a specific device by name.
+      audio_cat.info() << "Using OpenAL device " << dev_name << "\n";
+      _device = alcOpenDevice(dev_name.c_str());
+
+      if (_device == NULL) {
+        audio_cat.error()
+          << "Couldn't open OpenAL device \"" << dev_name << "\", falling back to default device\n";
+      }
     } else {
+      audio_cat.info() << "Using default OpenAL device\n";
+    }
+
+    if (_device == NULL) {
+      // Open the default device.
+      _device = alcOpenDevice(NULL);
+
+      if (_device == NULL && dev_name != "OpenAL Soft") {
+        // Try the OpenAL Soft driver instead, which is fairly reliable.
+        _device = alcOpenDevice("OpenAL Soft");
+
+        if (_device == NULL) {
+          audio_cat.error()
+            << "Couldn't open default OpenAL device\n";
+        }
+      }
+    }
+
+    if (_device != NULL) {
+      // We managed to get a device open.
       alcGetError(_device); // clear errors
       _context = alcCreateContext(_device, NULL);
-      alc_audio_errcheck("alcCreateContext(_device, NULL)",_device);
+      alc_audio_errcheck("alcCreateContext(_device, NULL)", _device);
       if (_context != NULL) {
         _openal_active = true;
       }
     }
   }
+
   // We increment _active_managers regardless of possible errors above.
   // The shutdown call will do the right thing when it's called,
   // either way.
@@ -150,15 +189,15 @@ OpenALAudioManager() {
     audio_3d_set_drop_off_factor(audio_drop_off_factor);
 
     if (audio_cat.is_debug()) {
-      audio_cat->debug()
+      audio_cat.debug()
         << "ALC_DEVICE_SPECIFIER:" << alcGetString(_device, ALC_DEVICE_SPECIFIER) << endl;
     }
   }
 
   if (audio_cat.is_debug()) {
-    audio_cat->debug() << "AL_RENDERER:" << alGetString(AL_RENDERER) << endl;
-    audio_cat->debug() << "AL_VENDOR:" << alGetString(AL_VENDOR) << endl;
-    audio_cat->debug() << "AL_VERSION:" << alGetString(AL_VERSION) << endl;
+    audio_cat.debug() << "AL_RENDERER:" << alGetString(AL_RENDERER) << endl;
+    audio_cat.debug() << "AL_VENDOR:" << alGetString(AL_VENDOR) << endl;
+    audio_cat.debug() << "AL_VERSION:" << alGetString(AL_VERSION) << endl;
   }
 }
 
@@ -212,6 +251,82 @@ is_valid() {
   return _is_valid;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: OpenALAudioManager::select_audio_device
+//       Access: Private
+//  Description: Enumerate the audio devices, selecting the one that
+//               is most appropriate or has been selected by the user.
+////////////////////////////////////////////////////////////////////
+string OpenALAudioManager::
+select_audio_device() {
+  string selected_device = openal_device;
+
+  const char *devices = NULL;
+
+  // This extension gives us all audio paths on all drivers.
+  if (alcIsExtensionPresent(NULL, "ALC_ENUMERATE_ALL_EXT") == AL_TRUE) {
+    string default_device = alcGetString(NULL, ALC_DEFAULT_ALL_DEVICES_SPECIFIER);
+    devices = (const char *)alcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER);
+
+    if (devices) {
+      audio_cat.debug() << "All OpenAL devices:\n";
+
+      while (*devices) {
+        string device(devices);
+        devices += device.size() + 1;
+
+        if (audio_cat.is_debug()) {
+          if (device == selected_device) {
+            audio_cat.debug() << "  " << device << " [selected]\n";
+          } else if (device == default_device) {
+            audio_cat.debug() << "  " << device << " [default]\n";
+          } else {
+            audio_cat.debug() << "  " << device << "\n";
+          }
+        }
+      }
+    }
+  } else {
+    audio_cat.debug() << "ALC_ENUMERATE_ALL_EXT not supported\n";
+  }
+
+  // This extension just gives us generic driver names, like "OpenAL Soft"
+  // and "Generic Software", rather than individual outputs.
+  if (alcIsExtensionPresent(NULL, "ALC_ENUMERATION_EXT") == AL_TRUE) {
+    string default_device = alcGetString(NULL, ALC_DEFAULT_DEVICE_SPECIFIER);
+    devices = (const char *)alcGetString(NULL, ALC_DEVICE_SPECIFIER);
+
+    if (devices) {
+      audio_cat.debug() << "OpenAL drivers:\n";
+
+      while (*devices) {
+        string device(devices);
+        devices += device.size() + 1;
+
+        if (selected_device.empty() && device == "OpenAL Soft" &&
+            default_device == "Generic Software") {
+          // Prefer OpenAL Soft over the buggy Generic Software driver.
+          selected_device = "OpenAL Soft";
+        }
+
+        if (audio_cat.is_debug()) {
+          if (device == selected_device) {
+            audio_cat.debug() << "  " << device << " [selected]\n";
+          } else if (device == default_device) {
+            audio_cat.debug() << "  " << device << " [default]\n";
+          } else {
+            audio_cat.debug() << "  " << device << "\n";
+          }
+        }
+      }
+    }
+  } else {
+    audio_cat.debug() << "ALC_ENUMERATION_EXT not supported\n";
+  }
+
+  return selected_device;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: OpenALAudioManager::make_current
 //       Access: Private

+ 2 - 0
panda/src/audiotraits/openalAudioManager.h

@@ -116,6 +116,8 @@ class EXPCL_OPENAL_AUDIO OpenALAudioManager : public AudioManager {
   virtual void update();
 
 private:
+  string select_audio_device();
+
   void make_current() const;
 
   bool can_use_audio(MovieAudioCursor *source);

+ 1 - 1
panda/src/bullet/bulletTranslationalLimitMotor.I

@@ -177,7 +177,7 @@ set_stop_erp(const LVecBase3 &erp) {
 INLINE int BulletTranslationalLimitMotor::
 get_current_limit(int axis) const {
 
-  nassertr((0 <- axis) && (axis <=2), false);
+  nassertr((0 <= axis) && (axis <= 2), false);
   return _motor.m_currentLimit[axis];
 }
 

+ 55 - 7
panda/src/cocoadisplay/cocoaGraphicsWindow.mm

@@ -798,16 +798,20 @@ set_properties_now(WindowProperties &properties) {
     int height = properties.get_y_size();
 
     if (!_properties.get_fullscreen()) {
-      _properties.set_size(width, height);
       if (_window != nil) {
         [_window setContentSize:NSMakeSize(width, height)];
       }
       [_view setFrameSize:NSMakeSize(width, height)];
 
-      cocoadisplay_cat.debug()
-        << "Setting size to " << width << ", " << height << "\n";
+      if (cocoadisplay_cat.is_debug()) {
+        cocoadisplay_cat.debug()
+          << "Setting size to " << width << ", " << height << "\n";
+      }
 
-      _context_needs_update = true;
+      // Cocoa doesn't send an event, and the other
+      // resize-window handlers will do nothing once the properties
+      // have been changed, so do this now
+      handle_resize_event();
       properties.clear_size();
 
     } else {
@@ -1022,6 +1026,23 @@ set_properties_now(WindowProperties &properties) {
     }
     properties.clear_z_order();
   }
+
+  if (properties.has_mouse_mode()) {
+    switch (properties.get_mouse_mode()) {
+    case WindowProperties::M_absolute:
+    case WindowProperties::M_confined:  // confined is maintained in mouse move event
+      CGAssociateMouseAndMouseCursorPosition(true);
+      _properties.set_mouse_mode(properties.get_mouse_mode());
+      properties.clear_mouse_mode();
+      break;
+
+    case WindowProperties::M_relative:
+      CGAssociateMouseAndMouseCursorPosition(false);
+      _properties.set_mouse_mode(properties.get_mouse_mode());
+      properties.clear_mouse_mode();
+      break;
+    }
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -1670,6 +1691,8 @@ handle_mouse_button_event(int button, bool down) {
 ////////////////////////////////////////////////////////////////////
 void CocoaGraphicsWindow::
 handle_mouse_moved_event(bool in_window, double x, double y, bool absolute) {
+  double nx, ny;
+
   if (absolute) {
     if (cocoadisplay_cat.is_spam()) {
       if (in_window != _input_devices[0].get_pointer().get_in_window()) {
@@ -1682,15 +1705,40 @@ handle_mouse_moved_event(bool in_window, double x, double y, bool absolute) {
     }
 
     // Strangely enough, in Cocoa, mouse Y coordinates are 1-based.
-    _input_devices[0].set_pointer(in_window, x, y - 1,
-      ClockObject::get_global_clock()->get_frame_time());
+    nx = x;
+    ny = y - 1;
 
   } else {
     // We received deltas, so add it to the current mouse position.
     MouseData md = _input_devices[0].get_pointer();
-    _input_devices[0].set_pointer_in_window(md.get_x() + x, md.get_y() + y);
+    nx = md.get_x() + x;
+    ny = md.get_y() + y;
+  }
+
+  if (_properties.get_mouse_mode() == WindowProperties::M_confined
+      && !in_window) {
+    CGPoint point;
+
+    nx = std::max(0., std::min((double) get_x_size() - 1, nx));
+    ny = std::max(0., std::min((double) get_y_size() - 1, ny));
+
+    if (_properties.get_fullscreen()) {
+      point = CGPointMake(nx, ny + 1);
+    } else {
+      point = CGPointMake(nx + _properties.get_x_origin(),
+                          ny + _properties.get_y_origin() + 1);
+    }
+
+    if (CGWarpMouseCursorPosition(point) == kCGErrorSuccess) {
+      in_window = true;
+    } else {
+      cocoadisplay_cat.warning() << "Failed to return mouse pointer to window\n";
+    }
   }
 
+  _input_devices[0].set_pointer(in_window, nx, ny,
+      ClockObject::get_global_clock()->get_frame_time());
+
   if (in_window != _mouse_hidden && _properties.get_cursor_hidden()) {
     // Hide the cursor if the mouse enters the window,
     // and unhide it when the mouse leaves the window.

+ 8 - 3
panda/src/cocoadisplay/cocoaPandaView.mm

@@ -45,8 +45,10 @@
 - (void) drawRect:(NSRect)dirtyRect {
   // Do nothing.  We draw from another thread.
 
-  cocoadisplay_cat.spam()
-    << "drawRect was called.\n";
+  if (cocoadisplay_cat.is_spam()) {
+    cocoadisplay_cat.spam()
+      << "drawRect was called.\n";
+  }
 }
 
 - (void) finalize {
@@ -116,7 +118,10 @@
   NSPoint loc = [self convertPoint:[event locationInWindow] fromView:nil];
   BOOL inside = [self mouse:loc inRect:[self bounds]];
 
-  if (_graphicsWindow->get_properties().get_mouse_mode() == WindowProperties::M_relative) {
+  // the correlation between mouse deltas and location
+  // are "debounced" apparently, so send deltas for both
+  // relative and confined modes
+  if (_graphicsWindow->get_properties().get_mouse_mode() != WindowProperties::M_absolute) {
     _graphicsWindow->handle_mouse_moved_event(inside, [event deltaX], [event deltaY], false);
   } else {
     _graphicsWindow->handle_mouse_moved_event(inside, loc.x, loc.y, true);

+ 19 - 0
panda/src/osxdisplay/osxGraphicsWindow.mm

@@ -2064,6 +2064,25 @@ set_properties_now(WindowProperties &properties) {
     properties.clear_minimized();
   }
 
+  if (properties.has_mouse_mode()) {
+    switch (properties.get_mouse_mode()) {
+    case WindowProperties::M_absolute:
+      CGAssociateMouseAndMouseCursorPosition(true);
+      _properties.set_mouse_mode(WindowProperties::M_absolute);
+      properties.clear_mouse_mode();
+      break;
+
+    case WindowProperties::M_relative:
+      CGAssociateMouseAndMouseCursorPosition(false);
+      _properties.set_mouse_mode(WindowProperties::M_relative);
+      properties.clear_mouse_mode();
+      break;
+
+    case WindowProperties::M_confined:
+      break;
+    }
+  }
+
   if (osxdisplay_cat.is_debug()) {
     osxdisplay_cat.debug()
       << "set_properties_now Out....." << _properties << "\n";

+ 8 - 0
panda/src/rocket/config_rocket.cxx

@@ -53,6 +53,10 @@ init_librocket() {
   RocketInputHandler::init_type();
   RocketRegion::init_type();
 
+  if (rocket_cat->is_debug()) {
+    rocket_cat->debug() << "Initializing libRocket library.\n";
+  }
+
   RocketFileInterface* fi = new RocketFileInterface;
   Rocket::Core::SetFileInterface(fi);
 
@@ -61,6 +65,10 @@ init_librocket() {
 
   Rocket::Core::Initialise();
 
+  // Register that we have the libRocket system.
+  PandaSystem *ps = PandaSystem::get_global_ptr();
+  ps->add_system("libRocket");
+
 #ifdef COMPILE_IN_DEFAULT_FONT
 #ifdef HAVE_FREETYPE
   // Load Panda's default compiled-in freetype font (Perspective Sans).

+ 11 - 9
samples/mouse-modes/main.py

@@ -34,7 +34,8 @@ class App(ShowBase):
         # Disable the camera trackball controls.
         self.disableMouse()
         
-        self.mouseMagnitude = 144
+        # control mapping of mouse movement to box movement
+        self.mouseMagnitude = 1
 
         self.rotateX, self.rotateY = 0, 0
 
@@ -66,7 +67,7 @@ class App(ShowBase):
         self.manualRecenterMouse = True
 
         # make a box to move with the mouse
-        self.model = self.loader.loadModel("box.egg")
+        self.model = self.loader.loadModel("box")
         self.model.reparentTo(self.render)
         
         self.cam.setPos(0, -5, 0)
@@ -75,7 +76,7 @@ class App(ShowBase):
         self.mouseTask = taskMgr.add(self.mouseTask, "Mouse Task")
         
     def setMouseMode(self, mode):
-        print "Changing mode to",mode
+        print("Changing mode to %s" % mode)
         
         self.mouseMode = mode
         
@@ -91,7 +92,7 @@ class App(ShowBase):
         
         actualMode = wp.getMouseMode()
         if self.mouseMode != actualMode:
-            print "ACTUAL MOUSE MODE:", actualMode
+            print("ACTUAL MOUSE MODE: %s" % actualMode)
             
         self.mouseMode = actualMode
         
@@ -106,11 +107,11 @@ class App(ShowBase):
             
 
     def toggleRecenter(self):
-        print "Toggling re-center behavior"
+        print("Toggling re-center behavior")
         self.manualRecenterMouse = not self.manualRecenterMouse
         
     def toggleMouse(self):
-        print "Toggling mouse visibility"
+        print("Toggling mouse visibility")
 
         self.hideMouse = not self.hideMouse
         
@@ -146,7 +147,8 @@ class App(ShowBase):
         if self.manualRecenterMouse:
             # move mouse back to center
             self.recenterMouse()             
-
+            self.lastMouseX, self.lastMouseY = 0, 0  
+                
         # scale position and delta to pixels for user
         w, h = self.win.getSize()
         
@@ -158,8 +160,8 @@ class App(ShowBase):
              int(dx*w), int(dy*h))) 
 
         # rotate box by delta
-        self.rotateX += dx * 10
-        self.rotateY += dy * 10
+        self.rotateX += dx * 10 * self.mouseMagnitude
+        self.rotateY += dy * 10 * self.mouseMagnitude
 
         self.positionText.setText("Model rotation: {0}, {1}".format(
              int(self.rotateX*1000)/1000., int(self.rotateY*1000)/1000.))