Browse Source

Merge branch 'master' into cmake

Sam Edwards 6 years ago
parent
commit
039f5af34e
100 changed files with 1893 additions and 351 deletions
  1. 4 1
      .gitignore
  2. 1 0
      BACKERS.md
  3. 4 4
      README.md
  4. 13 1
      direct/src/dist/commands.py
  5. 14 5
      direct/src/filter/FilterManager.py
  6. 18 3
      direct/src/gui/DirectOptionMenu.py
  7. 2 3
      direct/src/showbase/SfxPlayer.py
  8. 18 6
      direct/src/showbase/ShowBase.py
  9. 0 23
      direct/src/stdpy/threading.py
  10. 8 4
      direct/src/tkpanels/AnimPanel.py
  11. 32 17
      direct/src/tkwidgets/Valuator.py
  12. 37 0
      doc/ReleaseNotes
  13. 96 43
      dtool/src/dtoolutil/executionEnvironment.cxx
  14. 317 0
      dtool/src/dtoolutil/iostream_ext.cxx
  15. 53 0
      dtool/src/dtoolutil/iostream_ext.h
  16. 1 0
      dtool/src/dtoolutil/p3dtoolutil_ext_composite.cxx
  17. 12 5
      dtool/src/interrogate/interfaceMakerPythonNative.cxx
  18. 26 13
      dtool/src/parser-inc/iostream
  19. 20 0
      dtool/src/prc/encryptStream.cxx
  20. 3 0
      dtool/src/prc/encryptStream.h
  21. 18 0
      dtool/src/prc/encryptStreamBuf.I
  22. 54 3
      dtool/src/prc/encryptStreamBuf.cxx
  23. 9 0
      dtool/src/prc/encryptStreamBuf.h
  24. 25 1
      dtool/src/prc/streamReader.I
  25. 2 0
      dtool/src/prc/streamReader.h
  26. 28 1
      dtool/src/prc/streamWriter.I
  27. 2 0
      dtool/src/prc/streamWriter.h
  28. 31 6
      makepanda/makepanda.py
  29. 15 2
      makepanda/makewheel.py
  30. 16 13
      makepanda/test_wheel.py
  31. 1 1
      panda/src/audiotraits/openalAudioManager.cxx
  32. 15 5
      panda/src/audiotraits/openalAudioSound.cxx
  33. 14 0
      panda/src/bullet/bulletRigidBodyNode.cxx
  34. 1 0
      panda/src/bullet/bulletRigidBodyNode.h
  35. 1 1
      panda/src/cocoadisplay/cocoaGraphicsBuffer.h
  36. 1 1
      panda/src/cocoadisplay/cocoaGraphicsPipe.h
  37. 1 1
      panda/src/cocoadisplay/cocoaGraphicsStateGuardian.h
  38. 1 1
      panda/src/cocoadisplay/cocoaGraphicsWindow.h
  39. 2 2
      panda/src/cocoadisplay/cocoaGraphicsWindow.mm
  40. 1 1
      panda/src/collide/collisionTraverser.h
  41. 61 11
      panda/src/device/evdevInputDevice.cxx
  42. 4 1
      panda/src/device/ioKitInputDevice.cxx
  43. 3 0
      panda/src/device/linuxInputDeviceManager.cxx
  44. 42 7
      panda/src/device/winRawInputDevice.cxx
  45. 8 0
      panda/src/display/graphicsStateGuardian.cxx
  46. 1 0
      panda/src/display/graphicsStateGuardian.h
  47. 1 4
      panda/src/express/multifile.cxx
  48. 2 2
      panda/src/express/zStreamBuf.cxx
  49. 2 1
      panda/src/ffmpeg/ffmpegAudioCursor.cxx
  50. 1 1
      panda/src/ffmpeg/ffmpegAudioCursor.h
  51. 3 1
      panda/src/ffmpeg/ffmpegVideoCursor.cxx
  52. 25 2
      panda/src/glstuff/glGraphicsStateGuardian_src.cxx
  53. 24 20
      panda/src/glstuff/glShaderContext_src.cxx
  54. 5 0
      panda/src/glstuff/glmisc_src.cxx
  55. 1 0
      panda/src/glstuff/glmisc_src.h
  56. 12 1
      panda/src/glxdisplay/glxGraphicsStateGuardian.cxx
  57. 0 8
      panda/src/linmath/lmatrix4_src.I
  58. 7 0
      panda/src/mathutil/perlinNoise2.cxx
  59. 4 2
      panda/src/movies/flacAudioCursor.cxx
  60. 1 1
      panda/src/movies/flacAudioCursor.h
  61. 3 2
      panda/src/movies/microphoneAudioDS.cxx
  62. 18 12
      panda/src/movies/movieAudioCursor.cxx
  63. 2 2
      panda/src/movies/movieAudioCursor.h
  64. 12 0
      panda/src/movies/movieTypeRegistry.cxx
  65. 55 12
      panda/src/movies/opusAudioCursor.cxx
  66. 2 10
      panda/src/movies/opusAudioCursor.h
  67. 8 5
      panda/src/movies/userDataAudio.cxx
  68. 2 2
      panda/src/movies/userDataAudio.h
  69. 6 3
      panda/src/movies/userDataAudioCursor.cxx
  70. 1 1
      panda/src/movies/userDataAudioCursor.h
  71. 56 9
      panda/src/movies/vorbisAudioCursor.cxx
  72. 1 10
      panda/src/movies/vorbisAudioCursor.h
  73. 46 10
      panda/src/movies/wavAudioCursor.cxx
  74. 1 1
      panda/src/movies/wavAudioCursor.h
  75. 1 0
      panda/src/ode/odeBody.h
  76. 1 1
      panda/src/ode/odeJoint.h
  77. 18 1
      panda/src/ode/odeJoint_ext.cxx
  78. 1 1
      panda/src/ode/odeJoint_ext.h
  79. 51 0
      panda/src/pgraph/shaderAttrib.cxx
  80. 1 0
      panda/src/pgraph/shaderAttrib.h
  81. 5 1
      panda/src/pgui/pgButton.cxx
  82. 2 0
      panda/src/pipeline/mutexDebug.I
  83. 2 0
      panda/src/pipeline/mutexDirect.I
  84. 4 0
      panda/src/pipeline/pmutex.h
  85. 53 0
      panda/src/pipeline/pmutex_ext.I
  86. 41 0
      panda/src/pipeline/pmutex_ext.h
  87. 4 0
      panda/src/pipeline/reMutex.h
  88. 4 0
      panda/src/pipeline/reMutexDirect.I
  89. 53 0
      panda/src/pipeline/reMutex_ext.I
  90. 41 0
      panda/src/pipeline/reMutex_ext.h
  91. 8 7
      panda/src/putil/sparseArray.cxx
  92. 12 1
      panda/src/wgldisplay/wglGraphicsStateGuardian.cxx
  93. 5 0
      panda/src/windisplay/config_windisplay.cxx
  94. 1 0
      panda/src/windisplay/config_windisplay.h
  95. 1 1
      panda/src/windisplay/winGraphicsWindow.cxx
  96. 8 30
      panda/src/x11display/x11GraphicsWindow.cxx
  97. 45 0
      tests/bullet/test_bullet_heightfield.py
  98. 30 0
      tests/display/test_glsl_shader.py
  99. 128 0
      tests/dtoolutil/test_iostream.py
  100. 36 0
      tests/linmath/test_lmatrix4.py

+ 4 - 1
.gitignore

@@ -4,11 +4,12 @@
 /targetroot/
 /targetroot/
 /dstroot/
 /dstroot/
 
 
-# Core dumps
+# Core dumps and traces
 core
 core
 core.*
 core.*
 vgcore.*
 vgcore.*
 *.core
 *.core
+*.trace
 
 
 # Editor files/directories
 # Editor files/directories
 *.save
 *.save
@@ -26,6 +27,7 @@ vgcore.*
 /+DESC
 /+DESC
 /+MANIFEST
 /+MANIFEST
 /pkg-plist
 /pkg-plist
+/debug.ks
 
 
 # Produced installer/executables
 # Produced installer/executables
 /*.exe
 /*.exe
@@ -36,6 +38,7 @@ vgcore.*
 /*.dmg
 /*.dmg
 /*.whl
 /*.whl
 /*.txz
 /*.txz
+/*.apk
 
 
 # CMake
 # CMake
 /build/
 /build/

+ 1 - 0
BACKERS.md

@@ -22,6 +22,7 @@ This is a list of all the people who are contributing financially to Panda3D.  I
 ![Benefactors](https://opencollective.com/panda3d/tiers/benefactor.svg?avatarHeight=48&width=600)
 ![Benefactors](https://opencollective.com/panda3d/tiers/benefactor.svg?avatarHeight=48&width=600)
 
 
 * Sam Edwards
 * Sam Edwards
+* Max Voss
 
 
 ## Backers
 ## Backers
 
 

+ 4 - 4
README.md

@@ -24,7 +24,7 @@ Installing Panda3D
 ==================
 ==================
 
 
 The latest Panda3D SDK can be downloaded from
 The latest Panda3D SDK can be downloaded from
-[this page](https://www.panda3d.org/download/sdk-1-10-2/).
+[this page](https://www.panda3d.org/download/sdk-1-10-3/).
 If you are familiar with installing Python packages, you can use
 If you are familiar with installing Python packages, you can use
 the following comand:
 the following comand:
 
 
@@ -64,8 +64,8 @@ depending on whether you are on a 32-bit or 64-bit system, or you can
 [click here](https://github.com/rdb/panda3d-thirdparty) for instructions on
 [click here](https://github.com/rdb/panda3d-thirdparty) for instructions on
 building them from source.
 building them from source.
 
 
-https://www.panda3d.org/download/panda3d-1.10.2/panda3d-1.10.2-tools-win64.zip
-https://www.panda3d.org/download/panda3d-1.10.2/panda3d-1.10.2-tools-win32.zip
+https://www.panda3d.org/download/panda3d-1.10.3/panda3d-1.10.3-tools-win64.zip
+https://www.panda3d.org/download/panda3d-1.10.3/panda3d-1.10.3-tools-win32.zip
 
 
 After acquiring these dependencies, you may simply build Panda3D from the
 After acquiring these dependencies, you may simply build Panda3D from the
 command prompt using the following command.  (Change `14.1` to `14` if you are
 command prompt using the following command.  (Change `14.1` to `14` if you are
@@ -135,7 +135,7 @@ macOS
 -----
 -----
 
 
 On macOS, you will need to download a set of precompiled thirdparty packages in order to
 On macOS, 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.10.2/panda3d-1.10.2-tools-mac.tar.gz).
+compile Panda3D, which can be acquired from [here](https://www.panda3d.org/download/panda3d-1.10.3/panda3d-1.10.3-tools-mac.tar.gz).
 
 
 After placing the thirdparty directory inside the panda3d source directory,
 After placing the thirdparty directory inside the panda3d source directory,
 you may build Panda3D using a command like the following:
 you may build Panda3D using a command like the following:

+ 13 - 1
direct/src/dist/commands.py

@@ -13,6 +13,7 @@ import stat
 import struct
 import struct
 import imp
 import imp
 import string
 import string
+import time
 
 
 import setuptools
 import setuptools
 import distutils.log
 import distutils.log
@@ -30,6 +31,15 @@ if sys.version_info < (3, 0):
     # Python 3 defines these subtypes of IOError, but Python 2 doesn't.
     # Python 3 defines these subtypes of IOError, but Python 2 doesn't.
     FileNotFoundError = IOError
     FileNotFoundError = IOError
 
 
+    # Warn the user.  They might be using Python 2 by accident.
+    print("=================================================================")
+    print("WARNING: You are using Python 2, which will soon be discontinued.")
+    print("WARNING: Please use Python 3 for best results and continued")
+    print("WARNING: support after the EOL date of December 31st, 2019.")
+    print("=================================================================")
+    sys.stdout.flush()
+    time.sleep(4.0)
+
 
 
 def _parse_list(input):
 def _parse_list(input):
     if isinstance(input, basestring):
     if isinstance(input, basestring):
@@ -229,7 +239,9 @@ class build_apps(setuptools.Command):
         self.requirements_path = os.path.join(os.getcwd(), 'requirements.txt')
         self.requirements_path = os.path.join(os.getcwd(), 'requirements.txt')
         self.use_optimized_wheels = True
         self.use_optimized_wheels = True
         self.optimized_wheel_index = ''
         self.optimized_wheel_index = ''
-        self.pypi_extra_indexes = []
+        self.pypi_extra_indexes = [
+            'https://archive.panda3d.org/thirdparty',
+        ]
         self.file_handlers = {}
         self.file_handlers = {}
         self.exclude_dependencies = [
         self.exclude_dependencies = [
             # Windows
             # Windows

+ 14 - 5
direct/src/filter/FilterManager.py

@@ -124,7 +124,7 @@ class FilterManager(DirectObject):
 
 
         return winx,winy
         return winx,winy
 
 
-    def renderSceneInto(self, depthtex=None, colortex=None, auxtex=None, auxbits=0, textures=None):
+    def renderSceneInto(self, depthtex=None, colortex=None, auxtex=None, auxbits=0, textures=None, fbprops=None):
 
 
         """ Causes the scene to be rendered into the supplied textures
         """ Causes the scene to be rendered into the supplied textures
         instead of into the original window.  Puts a fullscreen quad
         instead of into the original window.  Puts a fullscreen quad
@@ -185,7 +185,10 @@ class FilterManager(DirectObject):
         # Choose the size of the offscreen buffer.
         # Choose the size of the offscreen buffer.
 
 
         (winx, winy) = self.getScaledSize(1,1,1)
         (winx, winy) = self.getScaledSize(1,1,1)
-        buffer = self.createBuffer("filter-base", winx, winy, texgroup)
+        if fbprops is not None:
+            buffer = self.createBuffer("filter-base", winx, winy, texgroup, fbprops=fbprops)
+        else:
+            buffer = self.createBuffer("filter-base", winx, winy, texgroup)
 
 
         if (buffer == None):
         if (buffer == None):
             return None
             return None
@@ -236,7 +239,7 @@ class FilterManager(DirectObject):
 
 
         return quad
         return quad
 
 
-    def renderQuadInto(self, name="filter-stage", mul=1, div=1, align=1, depthtex=None, colortex=None, auxtex0=None, auxtex1=None):
+    def renderQuadInto(self, name="filter-stage", mul=1, div=1, align=1, depthtex=None, colortex=None, auxtex0=None, auxtex1=None, fbprops=None):
 
 
         """ Creates an offscreen buffer for an intermediate
         """ Creates an offscreen buffer for an intermediate
         computation. Installs a quad into the buffer.  Returns
         computation. Installs a quad into the buffer.  Returns
@@ -250,7 +253,10 @@ class FilterManager(DirectObject):
 
 
         depthbits = bool(depthtex != None)
         depthbits = bool(depthtex != None)
 
 
-        buffer = self.createBuffer(name, winx, winy, texgroup, depthbits)
+        if fbprops is not None:
+            buffer = self.createBuffer(name, winx, winy, texgroup, depthbits, fbprops=fbprops)
+        else:
+            buffer = self.createBuffer(name, winx, winy, texgroup, depthbits)
 
 
         if (buffer == None):
         if (buffer == None):
             return None
             return None
@@ -287,7 +293,7 @@ class FilterManager(DirectObject):
 
 
         return quad
         return quad
 
 
-    def createBuffer(self, name, xsize, ysize, texgroup, depthbits=1):
+    def createBuffer(self, name, xsize, ysize, texgroup, depthbits=1, fbprops=None):
         """ Low-level buffer creation.  Not intended for public use. """
         """ Low-level buffer creation.  Not intended for public use. """
 
 
         winprops = WindowProperties()
         winprops = WindowProperties()
@@ -297,6 +303,9 @@ class FilterManager(DirectObject):
         props.setRgbColor(1)
         props.setRgbColor(1)
         props.setDepthBits(depthbits)
         props.setDepthBits(depthbits)
         props.setStereo(self.win.isStereo())
         props.setStereo(self.win.isStereo())
+        if fbprops is not None:
+            props.addProperties(fbprops)
+
         depthtex, colortex, auxtex0, auxtex1 = texgroup
         depthtex, colortex, auxtex0, auxtex1 = texgroup
         if (auxtex0 != None):
         if (auxtex0 != None):
             props.setAuxRgba(1)
             props.setAuxRgba(1)

+ 18 - 3
direct/src/gui/DirectOptionMenu.py

@@ -22,10 +22,12 @@ class DirectOptionMenu(DirectButton):
             # List of items to display on the popup menu
             # List of items to display on the popup menu
             ('items',       [],             self.setItems),
             ('items',       [],             self.setItems),
             # Initial item to display on menu button
             # Initial item to display on menu button
-            # Can be an interger index or the same string as the button
+            # Can be an integer index or the same string as the button
             ('initialitem', None,           DGG.INITOPT),
             ('initialitem', None,           DGG.INITOPT),
             # Amount of padding to place around popup button indicator
             # Amount of padding to place around popup button indicator
             ('popupMarkerBorder', (.1, .1), None),
             ('popupMarkerBorder', (.1, .1), None),
+            # The initial position of the popup marker
+            ('popupMarker_pos', (0, 0, 0), None),
             # Background color to use to highlight popup menu items
             # Background color to use to highlight popup menu items
             ('highlightColor', (.5, .5, .5, 1), None),
             ('highlightColor', (.5, .5, .5, 1), None),
             # Extra scale to use on highlight popup menu items
             # Extra scale to use on highlight popup menu items
@@ -42,6 +44,8 @@ class DirectOptionMenu(DirectButton):
         DirectButton.__init__(self, parent)
         DirectButton.__init__(self, parent)
         # Record any user specified frame size
         # Record any user specified frame size
         self.initFrameSize = self['frameSize']
         self.initFrameSize = self['frameSize']
+        # Record any user specified popup marker position
+        self.initPopupMarkerPos = self['popupMarker_pos']
         # Create a small rectangular marker to distinguish this button
         # Create a small rectangular marker to distinguish this button
         # as a popup menu button
         # as a popup menu button
         self.popupMarker = self.createcomponent(
         self.popupMarker = self.createcomponent(
@@ -168,8 +172,13 @@ class DirectOptionMenu(DirectButton):
         else:
         else:
             # Or base it upon largest item
             # Or base it upon largest item
             bounds = [self.minX, self.maxX, self.minZ, self.maxZ]
             bounds = [self.minX, self.maxX, self.minZ, self.maxZ]
-        pm.setPos(bounds[1] + pmw/2.0, 0,
-                  bounds[2] + (bounds[3] - bounds[2])/2.0)
+        if self.initPopupMarkerPos:
+            # Use specified position
+            pmPos = list(self.initPopupMarkerPos)
+        else:
+            # Or base the position on the frame size.
+            pmPos = [bounds[1] + pmw/2.0, 0, bounds[2] + (bounds[3] - bounds[2])/2.0]
+        pm.setPos(pmPos[0], pmPos[1], pmPos[2])
         # Adjust popup menu button to fit all items (or use user specified
         # Adjust popup menu button to fit all items (or use user specified
         # frame size
         # frame size
         bounds[1] += pmw
         bounds[1] += pmw
@@ -184,6 +193,12 @@ class DirectOptionMenu(DirectButton):
         Adjust popup position if default position puts it outside of
         Adjust popup position if default position puts it outside of
         visible screen region
         visible screen region
         """
         """
+
+        # Needed attributes (such as minZ) won't be set unless the user has specified
+        # items to display. Let's assert that we've given items to work with.
+        items = self['items']
+        assert items and len(items) > 0, 'Cannot show an empty popup menu! You must add items!'
+
         # Show the menu
         # Show the menu
         self.popupMenu.show()
         self.popupMenu.show()
         # Make sure its at the right scale
         # Make sure its at the right scale

+ 2 - 3
direct/src/showbase/SfxPlayer.py

@@ -53,6 +53,8 @@ class SfxPlayer:
                 d = node.getDistance(listenerNode)
                 d = node.getDistance(listenerNode)
             else:
             else:
                 d = node.getDistance(base.cam)
                 d = node.getDistance(base.cam)
+        if not cutoff:
+            cutoff = self.cutoffDistance
         if d == None or d > cutoff:
         if d == None or d > cutoff:
             volume = 0
             volume = 0
         else:
         else:
@@ -70,9 +72,6 @@ class SfxPlayer:
             self, sfx, looping = 0, interrupt = 1, volume = None,
             self, sfx, looping = 0, interrupt = 1, volume = None,
             time = 0.0, node=None, listenerNode = None, cutoff = None):
             time = 0.0, node=None, listenerNode = None, cutoff = None):
         if sfx:
         if sfx:
-            if not cutoff:
-                cutoff = self.cutoffDistance
-
             self.setFinalVolume(sfx, node, volume, listenerNode, cutoff)
             self.setFinalVolume(sfx, node, volume, listenerNode, cutoff)
 
 
             # don't start over if it's already playing, unless
             # don't start over if it's already playing, unless

+ 18 - 6
direct/src/showbase/ShowBase.py

@@ -829,9 +829,11 @@ class ShowBase(DirectObject.DirectObject):
             win.requestProperties(props)
             win.requestProperties(props)
 
 
         mainWindow = False
         mainWindow = False
-        if self.win == None:
+        if self.win is None:
             mainWindow = True
             mainWindow = True
             self.win = win
             self.win = win
+            if hasattr(self, 'bufferViewer'):
+                self.bufferViewer.win = win
 
 
         self.winList.append(win)
         self.winList.append(win)
 
 
@@ -1677,13 +1679,19 @@ class ShowBase(DirectObject.DirectObject):
         return self.mouseWatcherNode.getModifierButtons().isDown(
         return self.mouseWatcherNode.getModifierButtons().isDown(
             KeyboardButton.meta())
             KeyboardButton.meta())
 
 
-    def attachInputDevice(self, device, prefix=None):
+    def attachInputDevice(self, device, prefix=None, watch=False):
         """
         """
         This function attaches an input device to the data graph, which will
         This function attaches an input device to the data graph, which will
         cause the device to be polled and generate events.  If a prefix is
         cause the device to be polled and generate events.  If a prefix is
         given and not None, it is used to prefix events generated by this
         given and not None, it is used to prefix events generated by this
         device, separated by a hyphen.
         device, separated by a hyphen.
 
 
+        The watch argument can be set to True (as of Panda3D 1.10.3) to set up
+        the default MouseWatcher to receive inputs from this device, allowing
+        it to be polled via mouseWatcherNode and control user interfaces.
+        Setting this to True will also make it generate unprefixed events,
+        regardless of the specified prefix.
+
         If you call this, you should consider calling detachInputDevice when
         If you call this, you should consider calling detachInputDevice when
         you are done with the device or when it is disconnected.
         you are done with the device or when it is disconnected.
         """
         """
@@ -1694,13 +1702,17 @@ class ShowBase(DirectObject.DirectObject):
         idn = self.dataRoot.attachNewNode(InputDeviceNode(device, device.name))
         idn = self.dataRoot.attachNewNode(InputDeviceNode(device, device.name))
 
 
         # Setup the button thrower to generate events for the device.
         # Setup the button thrower to generate events for the device.
-        bt = idn.attachNewNode(ButtonThrower(device.name))
-        if prefix is not None:
-            bt.node().setPrefix(prefix + '-')
+        if prefix is not None or not watch:
+            bt = idn.attachNewNode(ButtonThrower(device.name))
+            if prefix is not None:
+                bt.node().setPrefix(prefix + '-')
+            self.deviceButtonThrowers.append(bt)
 
 
         assert self.notify.debug("Attached input device {0} with prefix {1}".format(device, prefix))
         assert self.notify.debug("Attached input device {0} with prefix {1}".format(device, prefix))
         self.__inputDeviceNodes[device] = idn
         self.__inputDeviceNodes[device] = idn
-        self.deviceButtonThrowers.append(bt)
+
+        if watch:
+            idn.node().addChild(self.mouseWatcherNode)
 
 
     def detachInputDevice(self, device):
     def detachInputDevice(self, device):
         """
         """

+ 0 - 23
direct/src/stdpy/threading.py

@@ -201,17 +201,6 @@ class Lock(core.Mutex):
     def __init__(self, name = "PythonLock"):
     def __init__(self, name = "PythonLock"):
         core.Mutex.__init__(self, name)
         core.Mutex.__init__(self, name)
 
 
-    def acquire(self, blocking = True):
-        if blocking:
-            core.Mutex.acquire(self)
-            return True
-        else:
-            return core.Mutex.tryAcquire(self)
-
-    __enter__ = acquire
-
-    def __exit__(self, t, v, tb):
-        self.release()
 
 
 class RLock(core.ReMutex):
 class RLock(core.ReMutex):
     """ This class provides a wrapper around Panda's ReMutex object.
     """ This class provides a wrapper around Panda's ReMutex object.
@@ -221,18 +210,6 @@ class RLock(core.ReMutex):
     def __init__(self, name = "PythonRLock"):
     def __init__(self, name = "PythonRLock"):
         core.ReMutex.__init__(self, name)
         core.ReMutex.__init__(self, name)
 
 
-    def acquire(self, blocking = True):
-        if blocking:
-            core.ReMutex.acquire(self)
-            return True
-        else:
-            return core.ReMutex.tryAcquire(self)
-
-    __enter__ = acquire
-
-    def __exit__(self, t, v, tb):
-        self.release()
-
 
 
 class Condition(core.ConditionVarFull):
 class Condition(core.ConditionVarFull):
     """ This class provides a wrapper around Panda's ConditionVarFull
     """ This class provides a wrapper around Panda's ConditionVarFull

+ 8 - 4
direct/src/tkpanels/AnimPanel.py

@@ -9,13 +9,16 @@ __all__ = ['AnimPanel', 'ActorControl']
 # Import Tkinter, Pmw, and the floater code from this directory tree.
 # Import Tkinter, Pmw, and the floater code from this directory tree.
 from direct.tkwidgets.AppShell import *
 from direct.tkwidgets.AppShell import *
 from direct.showbase.TkGlobal import *
 from direct.showbase.TkGlobal import *
-import Pmw, sys
+import Pmw, sys, os
 from direct.task import Task
 from direct.task import Task
+from panda3d.core import Filename, getModelPath
 
 
 if sys.version_info >= (3, 0):
 if sys.version_info >= (3, 0):
     from tkinter.simpledialog import askfloat
     from tkinter.simpledialog import askfloat
+    from tkinter.filedialog import askopenfilename
 else:
 else:
     from tkSimpleDialog import askfloat
     from tkSimpleDialog import askfloat
+    from tkFileDialog import askopenfilename
 
 
 
 
 FRAMES = 0
 FRAMES = 0
@@ -273,7 +276,7 @@ class AnimPanel(AppShell):
             title = 'Load Animation',
             title = 'Load Animation',
             parent = self.component('hull')
             parent = self.component('hull')
             )
             )
-        if (animFilename == ''):
+        if not animFilename:
             # no file selected, canceled
             # no file selected, canceled
             return
             return
 
 
@@ -369,8 +372,9 @@ class AnimPanel(AppShell):
     def destroy(self):
     def destroy(self):
         # First clean up
         # First clean up
         taskMgr.remove(self.id + '_UpdateTask')
         taskMgr.remove(self.id + '_UpdateTask')
-        self.destroyCallBack()
-        self.destroyCallBack = None
+        if self.destroyCallBack is not None:
+            self.destroyCallBack()
+            self.destroyCallBack = None
         AppShell.destroy(self)
         AppShell.destroy(self)
 
 
 class ActorControl(Pmw.MegaWidget):
 class ActorControl(Pmw.MegaWidget):

+ 32 - 17
direct/src/tkwidgets/Valuator.py

@@ -656,25 +656,35 @@ def rgbPanel(nodePath, callback = None, style = 'mini'):
     pButton.pack(expand = 1, fill = BOTH)
     pButton.pack(expand = 1, fill = BOTH)
 
 
     # Update menu
     # Update menu
-    menu = vgp.component('menubar').component('Valuator Group-menu')
+    menubar = vgp.component('menubar')
+    menubar.deletemenuitems('Valuator Group', 1, 1)
+
     # Some helper functions
     # Some helper functions
     # Clear color
     # Clear color
-    menu.insert_command(index = 1, label = 'Clear Color',
-                        command = lambda: nodePath.clearColor())
+    menubar.addmenuitem(
+        'Valuator Group', 'command',
+        label='Clear Color', command=lambda: nodePath.clearColor())
     # Set Clear Transparency
     # Set Clear Transparency
-    menu.insert_command(index = 2, label = 'Set Transparency',
-                        command = lambda: nodePath.setTransparency(1))
-    menu.insert_command(
-        index = 3, label = 'Clear Transparency',
-        command = lambda: nodePath.clearTransparency())
+    menubar.addmenuitem(
+        'Valuator Group', 'command',
+        label='Set Transparency', command=lambda: nodePath.setTransparency(1))
+    menubar.addmenuitem(
+        'Valuator Group', 'command',
+        label='Clear Transparency', command=lambda: nodePath.clearTransparency())
 
 
 
 
     # System color picker
     # System color picker
-    menu.insert_command(index = 4, label = 'Popup Color Picker',
-                        command = popupColorPicker)
+    menubar.addmenuitem(
+        'Valuator Group', 'command',
+        label='Popup Color Picker', command=popupColorPicker)
+
+    menubar.addmenuitem(
+        'Valuator Group', 'command',
+        label='Print to log', command=printToLog)
 
 
-    menu.insert_command(index = 5, label = 'Print to log',
-                        command = printToLog)
+    menubar.addmenuitem(
+        'Valuator Group', 'command', 'Dismiss Valuator Group panel',
+        label='Dismiss', command=vgp.destroy)
 
 
     def setNodePathColor(color):
     def setNodePathColor(color):
         nodePath.setColor(color[0]/255.0, color[1]/255.0,
         nodePath.setColor(color[0]/255.0, color[1]/255.0,
@@ -724,18 +734,23 @@ def lightRGBPanel(light, style = 'mini'):
     # Update menu button
     # Update menu button
     vgp.component('menubar').component('Valuator Group-button')['text'] = (
     vgp.component('menubar').component('Valuator Group-button')['text'] = (
         'Light Control Panel')
         'Light Control Panel')
+
     # Add a print button which will also serve as a color tile
     # Add a print button which will also serve as a color tile
     pButton = Button(vgp.interior(), text = 'Print to Log',
     pButton = Button(vgp.interior(), text = 'Print to Log',
                      bg = getTkColorString(initColor),
                      bg = getTkColorString(initColor),
                      command = printToLog)
                      command = printToLog)
     pButton.pack(expand = 1, fill = BOTH)
     pButton.pack(expand = 1, fill = BOTH)
+
     # Update menu
     # Update menu
-    menu = vgp.component('menubar').component('Valuator Group-menu')
+    menubar = vgp.component('menubar')
     # System color picker
     # System color picker
-    menu.insert_command(index = 4, label = 'Popup Color Picker',
-                        command = popupColorPicker)
-    menu.insert_command(index = 5, label = 'Print to log',
-                        command = printToLog)
+    menubar.addmenuitem(
+        'Valuator Group', 'command',
+        label='Popup Color Picker', command=popupColorPicker)
+    menubar.addmenuitem(
+        'Valuator Group', 'command',
+        label='Print to log', command=printToLog)
+
     def setLightColor(color):
     def setLightColor(color):
         light.setColor(Vec4(color[0]/255.0, color[1]/255.0,
         light.setColor(Vec4(color[0]/255.0, color[1]/255.0,
                             color[2]/255.0, color[3]/255.0))
                             color[2]/255.0, color[3]/255.0))

+ 37 - 0
doc/ReleaseNotes

@@ -1,3 +1,40 @@
+------------------------  RELEASE 1.10.3  -----------------------
+
+This is another bugfix release that addresses a variety of issues
+in 1.10.2 and further improves the stability.
+
+* Fix crash when unplugging certain devices on macOS
+* Fix crash on macOS when using RIME input
+* Fix logging issues/crashes in apps deployed with Python 2.7
+* Fix issues when starting in fullscreen on Linux/X11
+* Fix mapping of several gamepads including Trust GXT 24
+* Fix Linux crash when no input devices are present
+* Unbreak support for matrix arrays in vertex data in OpenGL
+* Allow creating multisample FBO in OpenGL with non-MS host window
+* Support playing and looping compressed Ogg and WAV audio files
+* Fix generation of CollisionBox for transformed geometry in .egg
+* Fix Bullet rigid body transform not updating after reparenting
+* Fix sporadic color scales with lighting and custom GLSL shader
+* Prevent faulty shaders from shutting down GSG on some drivers
+* Allow None as either argument to OdeJoint.attach()
+* Fix BufferViewer when main window is not opened right away
+* Properly detect extension of pz/gz compressed video/audio files
+* Fix for invalid behavior of SparseArray methods to clear bits
+* FilterManager now allows overriding framebuffer properties
+* Fix detection of core-only OpenGL profile on some drivers
+* Add gl-forward-compatible config var for OpenGL context creation
+* Add paste-emit-keystrokes variable to disable Ctrl+V on Windows
+* Fix in-place |= operator on Panda types (such as SparseArray)
+* Fix rare FFmpeg "bad src image pointers" errors after seek
+* Fix uses of types.InstanceType in some obscure direct functions
+* Fix capsule-into-sphere collision test in degenerate case
+* KeyboardButton.ascii_key now also accepts a str character
+* Fix errors in various Tkinter DIRECT widgets
+* Expose save_egg_file/save_egg_data functions in Python API
+* Fix assertion error in BoundingBox.set_min_max
+* Fix typo in CollisionTraverser.respect_prev_transform property
+* Properly install Python bindings when building FreeBSD installer
+
 ------------------------  RELEASE 1.10.2  -----------------------
 ------------------------  RELEASE 1.10.2  -----------------------
 
 
 This release fixes several more bugs, including a few regressions
 This release fixes several more bugs, including a few regressions

+ 96 - 43
dtool/src/dtoolutil/executionEnvironment.cxx

@@ -85,6 +85,44 @@ extern char **GLOBAL_ARGV;
 extern int GLOBAL_ARGC;
 extern int GLOBAL_ARGC;
 #endif
 #endif
 
 
+// One of the responsibilities of ExecutionEnvironment is to determine the path
+// to the binary file that contains itself (this is useful for making other
+// components able to read files relative to Panda's installation directory).
+// When built statically, this is easy - just use the main executable filename.
+// When built shared, ExecutionEnvironment will introspect the memory map of
+// the running process to look for dynamic library paths matching this list of
+// predetermined filenames (ordered most likely to least likely).
+
+#ifndef LINK_ALL_STATIC
+static const char *const libp3dtool_filenames[] = {
+#if defined(LIBP3DTOOL_FILENAMES)
+
+  // The build system is communicating the expected filename(s) for the
+  // libp3dtool dynamic library - no guesswork needed.
+  LIBP3DTOOL_FILENAMES
+
+#elif defined(WIN32_VC)
+
+#ifdef _DEBUG
+  "libp3dtool_d.dll",
+#else
+  "libp3dtool.dll",
+#endif
+
+#elif defined(__APPLE__)
+
+  "libp3dtool." PANDA_ABI_VERSION_STR ".dylib",
+  "libp3dtool.dylib",
+
+#else
+
+  "libp3dtool.so." PANDA_ABI_VERSION_STR,
+  "libp3dtool.so",
+
+#endif
+};
+#endif /* !LINK_ALL_STATIC */
+
 // Linux with GNU libc does have global argvargc variables, but we can't
 // Linux with GNU libc does have global argvargc variables, but we can't
 // safely access them at stat init time--at least, not in libc5. (It does seem
 // safely access them at stat init time--at least, not in libc5. (It does seem
 // to work with glibc2, however.)
 // to work with glibc2, however.)
@@ -546,13 +584,14 @@ read_args() {
   // First, we need to fill in _dtool_name.  This contains the full path to
   // First, we need to fill in _dtool_name.  This contains the full path to
   // the p3dtool library.
   // the p3dtool library.
 
 
-#ifdef WIN32_VC
-#ifdef _DEBUG
-  HMODULE dllhandle = GetModuleHandle("libp3dtool_d.dll");
-#else
-  HMODULE dllhandle = GetModuleHandle("libp3dtool.dll");
-#endif
-  if (dllhandle != 0) {
+#ifndef LINK_ALL_STATIC
+#if defined(WIN32_VC)
+  for (const char *filename : libp3dtool_filenames) {
+    if (!_dtool_name.empty()) break;
+
+    HMODULE dllhandle = GetModuleHandle(filename);
+    if (!dllhandle) continue;
+
     static const DWORD buffer_size = 1024;
     static const DWORD buffer_size = 1024;
     wchar_t buffer[buffer_size];
     wchar_t buffer[buffer_size];
     DWORD size = GetModuleFileNameW(dllhandle, buffer, buffer_size);
     DWORD size = GetModuleFileNameW(dllhandle, buffer, buffer_size);
@@ -562,46 +601,44 @@ read_args() {
       _dtool_name = tmp;
       _dtool_name = tmp;
     }
     }
   }
   }
-#endif
 
 
-#if defined(__APPLE__)
+#elif defined(__APPLE__)
   // And on OSX we don't have procselfmaps, but some _dyld_* functions.
   // And on OSX we don't have procselfmaps, but some _dyld_* functions.
 
 
-  if (_dtool_name.empty()) {
-    uint32_t ic = _dyld_image_count();
-    for (uint32_t i = 0; i < ic; ++i) {
-      const char *buffer = _dyld_get_image_name(i);
-      const char *tail = strrchr(buffer, '/');
-      if (tail && (strcmp(tail, "/libp3dtool." PANDA_ABI_VERSION_STR ".dylib") == 0
-                || strcmp(tail, "/libp3dtool.dylib") == 0)) {
+  uint32_t ic = _dyld_image_count();
+  for (uint32_t i = 0; i < ic; ++i) {
+    if (!_dtool_name.empty()) break;
+
+    const char *buffer = _dyld_get_image_name(i);
+    if (!buffer) continue;
+    const char *tail = strrchr(buffer, '/');
+    if (!tail) continue;
+
+    for (const char *filename : libp3dtool_filenames) {
+      if (strcmp(&tail[1], filename) == 0) {
         _dtool_name = buffer;
         _dtool_name = buffer;
+        break;
       }
       }
     }
     }
   }
   }
-#endif
 
 
-#if defined(RTLD_DI_ORIGIN)
+#elif defined(RTLD_DI_ORIGIN)
   // When building with glibc/uClibc, we typically have access to RTLD_DI_ORIGIN in Unix-like operating systems.
   // When building with glibc/uClibc, we typically have access to RTLD_DI_ORIGIN in Unix-like operating systems.
 
 
   char origin[PATH_MAX + 1];
   char origin[PATH_MAX + 1];
 
 
-  if (_dtool_name.empty()) {
-    void *dtool_handle = dlopen("libp3dtool.so." PANDA_ABI_VERSION_STR, RTLD_NOW | RTLD_NOLOAD);
+  for (const char *filename : libp3dtool_filenames) {
+    if (!_dtool_name.empty()) break;
+
+    void *dtool_handle = dlopen(filename, RTLD_NOW | RTLD_NOLOAD);
     if (dtool_handle != nullptr && dlinfo(dtool_handle, RTLD_DI_ORIGIN, origin) != -1) {
     if (dtool_handle != nullptr && dlinfo(dtool_handle, RTLD_DI_ORIGIN, origin) != -1) {
       _dtool_name = origin;
       _dtool_name = origin;
-      _dtool_name += "/libp3dtool.so." PANDA_ABI_VERSION_STR;
-    } else {
-      // Try the version of libp3dtool.so without ABI suffix.
-      dtool_handle = dlopen("libp3dtool.so", RTLD_NOW | RTLD_NOLOAD);
-      if (dtool_handle != nullptr && dlinfo(dtool_handle, RTLD_DI_ORIGIN, origin) != -1) {
-        _dtool_name = origin;
-        _dtool_name += "/libp3dtool.so";
-      }
+      _dtool_name += '/';
+      _dtool_name += filename;
     }
     }
   }
   }
-#endif
 
 
-#if !defined(RTLD_DI_ORIGIN) && defined(RTLD_DI_LINKMAP)
+#elif defined(RTLD_DI_LINKMAP)
   // On platforms without RTLD_DI_ORIGIN, we can use dlinfo with RTLD_DI_LINKMAP to get the origin of a loaded library.
   // On platforms without RTLD_DI_ORIGIN, we can use dlinfo with RTLD_DI_LINKMAP to get the origin of a loaded library.
   if (_dtool_name.empty()) {
   if (_dtool_name.empty()) {
     struct link_map *map;
     struct link_map *map;
@@ -610,16 +647,24 @@ read_args() {
 #else
 #else
     void *self = dlopen(NULL, RTLD_NOW | RTLD_NOLOAD);
     void *self = dlopen(NULL, RTLD_NOW | RTLD_NOLOAD);
 #endif
 #endif
-    dlinfo(self, RTLD_DI_LINKMAP, &map);
-
-    while (map != nullptr) {
-      const char *tail = strrchr(map->l_name, '/');
-      const char *head = strchr(map->l_name, '/');
-      if (tail && head && (strcmp(tail, "/libp3dtool.so." PANDA_ABI_VERSION_STR) == 0
-                        || strcmp(tail, "/libp3dtool.so") == 0)) {
-        _dtool_name = head;
+    if (dlinfo(self, RTLD_DI_LINKMAP, &map)) {
+      while (map != nullptr) {
+        if (!_dtool_name.empty()) break;
+
+        const char *head = strchr(map->l_name, '/');
+        if (!head) continue;
+        const char *tail = strrchr(head, '/');
+        if (!tail) continue;
+
+        for (const char *filename : libp3dtool_filenames) {
+          if (strcmp(&tail[1], filename) == 0) {
+            _dtool_name = head;
+            break;
+          }
+        }
+
+        map = map->l_next;
       }
       }
-      map = map->l_next;
     }
     }
   }
   }
 #endif
 #endif
@@ -634,19 +679,27 @@ read_args() {
     pifstream maps("/proc/self/maps");
     pifstream maps("/proc/self/maps");
 #endif
 #endif
     while (!maps.fail() && !maps.eof()) {
     while (!maps.fail() && !maps.eof()) {
+      if (!_dtool_name.empty()) break;
+
       char buffer[PATH_MAX];
       char buffer[PATH_MAX];
       buffer[0] = 0;
       buffer[0] = 0;
       maps.getline(buffer, PATH_MAX);
       maps.getline(buffer, PATH_MAX);
-      const char *tail = strrchr(buffer, '/');
       const char *head = strchr(buffer, '/');
       const char *head = strchr(buffer, '/');
-      if (tail && head && (strcmp(tail, "/libp3dtool.so." PANDA_ABI_VERSION_STR) == 0
-                        || strcmp(tail, "/libp3dtool.so") == 0)) {
-        _dtool_name = head;
+      if (!head) continue;
+      const char *tail = strrchr(head, '/');
+      if (!tail) continue;
+
+      for (const char *filename : libp3dtool_filenames) {
+        if (strcmp(&tail[1], filename) == 0) {
+          _dtool_name = head;
+          break;
+        }
       }
       }
     }
     }
     maps.close();
     maps.close();
   }
   }
 #endif
 #endif
+#endif /* !LINK_ALL_STATIC */
 
 
   // Now, we need to fill in _binary_name.  This contains the full path to the
   // Now, we need to fill in _binary_name.  This contains the full path to the
   // currently running executable.
   // currently running executable.

+ 317 - 0
dtool/src/dtoolutil/iostream_ext.cxx

@@ -0,0 +1,317 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file iostream_ext.cxx
+ * @author rdb
+ * @date 2017-07-24
+ */
+
+#include "iostream_ext.h"
+
+#ifdef HAVE_PYTHON
+
+#ifndef CPPPARSER
+extern struct Dtool_PyTypedObject Dtool_std_istream;
+#endif
+
+/**
+ * Reads the given number of bytes from the stream, returned as bytes object.
+ * If the given size is -1, all bytes are read from the stream.
+ */
+PyObject *Extension<istream>::
+read(int size) {
+  if (size < 0) {
+    return readall();
+  }
+
+  char *buffer;
+  std::streamsize read_bytes = 0;
+
+  if (size > 0) {
+    std::streambuf *buf = _this->rdbuf();
+    nassertr(buf != nullptr, nullptr);
+
+#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
+    PyThreadState *_save;
+    Py_UNBLOCK_THREADS
+#endif
+
+    buffer = (char *)alloca((size_t)size);
+    read_bytes = buf->sgetn(buffer, (size_t)size);
+
+#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
+    Py_BLOCK_THREADS
+#endif
+  }
+
+#if PY_MAJOR_VERSION >= 3
+  return PyBytes_FromStringAndSize(buffer, read_bytes);
+#else
+  return PyString_FromStringAndSize(buffer, read_bytes);
+#endif
+}
+
+/**
+ * Reads from the underlying stream, but using at most one call.  The number
+ * of returned bytes may therefore be less than what was requested, but it
+ * will always be greater than 0 until EOF is reached.
+ */
+PyObject *Extension<istream>::
+read1(int size) {
+  std::streambuf *buf = _this->rdbuf();
+  nassertr(buf != nullptr, nullptr);
+
+#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
+  PyThreadState *_save;
+  Py_UNBLOCK_THREADS
+#endif
+
+  std::streamsize avail = buf->in_avail();
+  if (avail == 0) {
+    avail = 4096;
+  }
+
+  if (size >= 0 && (std::streamsize)size < avail) {
+    avail = (std::streamsize)size;
+  }
+
+  // Don't read more than 4K at a time
+  if (avail > 4096) {
+    avail = 4096;
+  }
+
+  char *buffer = (char *)alloca(avail);
+  std::streamsize read_bytes = buf->sgetn(buffer, avail);
+
+#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
+  Py_BLOCK_THREADS
+#endif
+
+#if PY_MAJOR_VERSION >= 3
+  return PyBytes_FromStringAndSize(buffer, read_bytes);
+#else
+  return PyString_FromStringAndSize(buffer, read_bytes);
+#endif
+}
+
+/**
+ * Reads all of the bytes in the stream.
+ */
+PyObject *Extension<istream>::
+readall() {
+  std::streambuf *buf = _this->rdbuf();
+  nassertr(buf != nullptr, nullptr);
+
+#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
+  PyThreadState *_save;
+  Py_UNBLOCK_THREADS
+#endif
+
+  std::vector<unsigned char> result;
+
+  static const size_t buffer_size = 4096;
+  char buffer[buffer_size];
+
+  std::streamsize count = buf->sgetn(buffer, buffer_size);
+  while (count != 0) {
+    thread_consider_yield();
+    result.insert(result.end(), buffer, buffer + count);
+    count = buf->sgetn(buffer, buffer_size);
+  }
+
+#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
+  Py_BLOCK_THREADS
+#endif
+
+#if PY_MAJOR_VERSION >= 3
+  return PyBytes_FromStringAndSize((char *)result.data(), result.size());
+#else
+  return PyString_FromStringAndSize((char *)result.data(), result.size());
+#endif
+}
+
+/**
+ * Reads bytes into a preallocated, writable, bytes-like object, returning the
+ * number of bytes read.
+ */
+std::streamsize Extension<istream>::
+readinto(PyObject *b) {
+  std::streambuf *buf = _this->rdbuf();
+  nassertr(buf != nullptr, 0);
+
+  Py_buffer view;
+  if (PyObject_GetBuffer(b, &view, PyBUF_CONTIG) == -1) {
+    PyErr_SetString(PyExc_TypeError,
+      "write() requires a contiguous, read-write bytes-like object");
+    return 0;
+  }
+
+#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
+  PyThreadState *_save;
+  Py_UNBLOCK_THREADS
+#endif
+
+  std::streamsize count = buf->sgetn((char *)view.buf, view.len);
+
+#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
+  Py_BLOCK_THREADS
+#endif
+
+  PyBuffer_Release(&view);
+  return count;
+}
+
+/**
+ * Extracts one line up to and including the trailing newline character.
+ * Returns empty string when the end of file is reached.
+ */
+PyObject *Extension<istream>::
+readline(int size) {
+  std::streambuf *buf = _this->rdbuf();
+  nassertr(buf != nullptr, nullptr);
+
+#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
+  PyThreadState *_save;
+  Py_UNBLOCK_THREADS
+#endif
+
+  std::string line;
+  int ch = buf->sbumpc();
+  while (ch != EOF && (--size) != 0) {
+    line.push_back(ch);
+    if (ch == '\n') {
+      // Here's the newline character.
+      break;
+    }
+    ch = buf->sbumpc();
+  }
+
+#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
+  Py_BLOCK_THREADS
+#endif
+
+#if PY_MAJOR_VERSION >= 3
+  return PyBytes_FromStringAndSize(line.data(), line.size());
+#else
+  return PyString_FromStringAndSize(line.data(), line.size());
+#endif
+}
+
+/**
+ * Reads all the lines at once and returns a list.  Also see the documentation
+ * for readline().
+ */
+PyObject *Extension<istream>::
+readlines(int hint) {
+  PyObject *lst = PyList_New(0);
+  if (lst == nullptr) {
+    return nullptr;
+  }
+
+  PyObject *py_line = readline(-1);
+
+  if (hint < 0) {
+    while (Py_SIZE(py_line) > 0) {
+      PyList_Append(lst, py_line);
+      Py_DECREF(py_line);
+
+      py_line = readline(-1);
+    }
+  } else {
+    size_t totchars = 0;
+    while (Py_SIZE(py_line) > 0) {
+      totchars += Py_SIZE(py_line);
+      PyList_Append(lst, py_line);
+      Py_DECREF(py_line);
+
+      if (totchars > hint) {
+        break;
+      }
+
+      py_line = readline(-1);
+    }
+  }
+
+  return lst;
+}
+
+/**
+ * Yields continuously to read all the lines from the istream.
+ */
+static PyObject *gen_next(PyObject *self) {
+  istream *stream = nullptr;
+  if (!Dtool_Call_ExtractThisPointer(self, Dtool_std_istream, (void **)&stream)) {
+    return nullptr;
+  }
+
+  PyObject *line = invoke_extension(stream).readline();
+  if (Py_SIZE(line) > 0) {
+    return line;
+  } else {
+    PyErr_SetObject(PyExc_StopIteration, nullptr);
+    return nullptr;
+  }
+}
+
+/**
+ * Iterates over the lines of the file.
+ */
+PyObject *Extension<istream>::
+__iter__(PyObject *self) {
+  return Dtool_NewGenerator(self, &gen_next);
+}
+
+/**
+ * Writes the bytes object to the stream.
+ */
+void Extension<ostream>::
+write(PyObject *b) {
+  std::streambuf *buf = _this->rdbuf();
+  nassertv(buf != nullptr);
+
+  Py_buffer view;
+  if (PyObject_GetBuffer(b, &view, PyBUF_CONTIG_RO) == -1) {
+    PyErr_SetString(PyExc_TypeError, "write() requires a contiguous buffer");
+    return;
+  }
+
+#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
+  PyThreadState *_save;
+  Py_UNBLOCK_THREADS
+  buf->sputn((const char *)view.buf, view.len);
+  Py_BLOCK_THREADS
+#else
+  buf->sputn((const char *)view.buf, view.len);
+#endif
+
+  PyBuffer_Release(&view);
+}
+
+/**
+ * Write a list of lines to the stream.  Line separators are not added, so it
+ * is usual for each of the lines provided to have a line separator at the
+ * end.
+ */
+void Extension<ostream>::
+writelines(PyObject *lines) {
+  PyObject *seq = PySequence_Fast(lines, "writelines() expects a sequence");
+  if (seq == nullptr) {
+    return;
+  }
+
+  PyObject **items = PySequence_Fast_ITEMS(seq);
+  Py_ssize_t len = PySequence_Fast_GET_SIZE(seq);
+
+  for (Py_ssize_t i = 0; i < len; ++i) {
+    write(items[i]);
+  }
+
+  Py_DECREF(seq);
+}
+
+#endif  // HAVE_PYTHON

+ 53 - 0
dtool/src/dtoolutil/iostream_ext.h

@@ -0,0 +1,53 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file iostream_ext.h
+ * @author rdb
+ * @date 2017-07-24
+ */
+
+#ifndef IOSTREAM_EXT_H
+#define IOSTREAM_EXT_H
+
+#include "dtoolbase.h"
+
+#ifdef HAVE_PYTHON
+
+#include "extension.h"
+#include <iostream>
+#include "py_panda.h"
+
+/**
+ * These classes define the extension methods for istream and ostream, which
+ * are called instead of any C++ methods with the same prototype.
+ *
+ * These are designed to allow streams to be treated as file-like objects.
+ */
+template<>
+class Extension<istream> : public ExtensionBase<istream> {
+public:
+  PyObject *read(int size=-1);
+  PyObject *read1(int size=-1);
+  PyObject *readall();
+  std::streamsize readinto(PyObject *b);
+
+  PyObject *readline(int size=-1);
+  PyObject *readlines(int hint=-1);
+  PyObject *__iter__(PyObject *self);
+};
+
+template<>
+class Extension<ostream> : public ExtensionBase<ostream> {
+public:
+  void write(PyObject *b);
+  void writelines(PyObject *lines);
+};
+
+#endif  // HAVE_PYTHON
+
+#endif  // IOSTREAM_EXT_H

+ 1 - 0
dtool/src/dtoolutil/p3dtoolutil_ext_composite.cxx

@@ -1,3 +1,4 @@
 #include "filename_ext.cxx"
 #include "filename_ext.cxx"
 #include "globPattern_ext.cxx"
 #include "globPattern_ext.cxx"
+#include "iostream_ext.cxx"
 #include "textEncoder_ext.cxx"
 #include "textEncoder_ext.cxx"

+ 12 - 5
dtool/src/interrogate/interfaceMakerPythonNative.cxx

@@ -431,6 +431,12 @@ get_slotted_function_def(Object *obj, Function *func, FunctionRemap *remap,
     return true;
     return true;
   }
   }
 
 
+  if (method_name == "operator |=") {
+    def._answer_location = "nb_inplace_or";
+    def._wrapper_type = WT_inplace_binary_operator;
+    return true;
+  }
+
   if (method_name == "__ipow__") {
   if (method_name == "__ipow__") {
     def._answer_location = "nb_inplace_power";
     def._answer_location = "nb_inplace_power";
     def._wrapper_type = WT_inplace_ternary_operator;
     def._wrapper_type = WT_inplace_ternary_operator;
@@ -4946,13 +4952,14 @@ write_function_instance(ostream &out, FunctionRemap *remap,
       expected_params += "NoneType";
       expected_params += "NoneType";
 
 
     } else if (TypeManager::is_char(type)) {
     } else if (TypeManager::is_char(type)) {
-      indent(out, indent_level) << "char " << param_name << default_expr << ";\n";
+      indent(out, indent_level) << "char *" << param_name << "_str;\n";
+      indent(out, indent_level) << "Py_ssize_t " << param_name << "_len;\n";
 
 
-      format_specifiers += "c";
-      parameter_list += ", &" + param_name;
+      format_specifiers += "s#";
+      parameter_list += ", &" + param_name + "_str, &" + param_name + "_len";
+      extra_param_check << " && " << param_name << "_len == 1";
 
 
-      // extra_param_check << " && isascii(" << param_name << ")";
-      pexpr_string = "(char) " + param_name;
+      pexpr_string = param_name + "_str[0]";
       expected_params += "char";
       expected_params += "char";
       only_pyobjects = false;
       only_pyobjects = false;
 
 

+ 26 - 13
dtool/src/parser-inc/iostream

@@ -1,16 +1,15 @@
-// Filename: iostream
-// Created by:  drose (12May00)
-//
-////////////////////////////////////////////////////////////////////
-//
-// PANDA 3D SOFTWARE
-// Copyright (c) Carnegie Mellon University.  All rights reserved.
-//
-// All use of this software is subject to the terms of the revised BSD
-// license.  You should have received a copy of this license along
-// with this source code in a file named "LICENSE."
-//
-////////////////////////////////////////////////////////////////////
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file iostream
+ * @author drose
+ * @date 2000-05-12
+ */
 
 
 // This file, and all the other files in this directory, aren't
 // This file, and all the other files in this directory, aren't
 // intended to be compiled--they're just parsed by CPPParser (and
 // intended to be compiled--they're just parsed by CPPParser (and
@@ -34,6 +33,9 @@ namespace std {
   __published:
   __published:
     ostream(const ostream&) = delete;
     ostream(const ostream&) = delete;
 
 
+    __extension void write(PyObject *b);
+    __extension void writelines(PyObject *lines);
+
     void put(char c);
     void put(char c);
     void flush();
     void flush();
     streampos tellp();
     streampos tellp();
@@ -43,10 +45,20 @@ namespace std {
   protected:
   protected:
     ostream(ostream &&);
     ostream(ostream &&);
   };
   };
+
   class istream : virtual public ios {
   class istream : virtual public ios {
   __published:
   __published:
     istream(const istream&) = delete;
     istream(const istream&) = delete;
 
 
+    __extension PyObject *read(int size=-1);
+    __extension PyObject *read1(int size=-1);
+    __extension PyObject *readall();
+    __extension std::streamsize readinto(PyObject *b);
+
+    __extension PyObject *readline(int size=-1);
+    __extension PyObject *readlines(int hint=-1);
+    __extension PyObject *__iter__(PyObject *self);
+
     int get();
     int get();
     streampos tellg();
     streampos tellg();
     void seekg(streampos pos);
     void seekg(streampos pos);
@@ -55,6 +67,7 @@ namespace std {
   protected:
   protected:
     istream(istream &&);
     istream(istream &&);
   };
   };
+
   class iostream : public istream, public ostream {
   class iostream : public istream, public ostream {
   __published:
   __published:
     iostream(const iostream&) = delete;
     iostream(const iostream&) = delete;

+ 20 - 0
dtool/src/prc/encryptStream.cxx

@@ -12,3 +12,23 @@
  */
  */
 
 
 #include "encryptStream.h"
 #include "encryptStream.h"
+
+/**
+ * Must be called immediately after open_read().  Decrypts the given number of
+ * bytes and checks that they match.  The amount of header bytes are added to
+ * an offset so that skipping to 0 will skip past the header.
+ *
+ * Returns true if the read magic matches the given magic, false on error.
+ */
+bool IDecryptStream::
+read_magic(const char *magic, size_t size) {
+  char *this_magic = (char *)alloca(size);
+  read(this_magic, size);
+
+  if (!fail() && gcount() == size && memcmp(this_magic, magic, size) == 0) {
+    _buf.set_magic_length(size);
+    return true;
+  } else {
+    return false;
+  }
+}

+ 3 - 0
dtool/src/prc/encryptStream.h

@@ -53,6 +53,9 @@ PUBLISHED:
   MAKE_PROPERTY(key_length, get_key_length);
   MAKE_PROPERTY(key_length, get_key_length);
   MAKE_PROPERTY(iteration_count, get_iteration_count);
   MAKE_PROPERTY(iteration_count, get_iteration_count);
 
 
+public:
+  bool read_magic(const char *magic, size_t size);
+
 private:
 private:
   EncryptStreamBuf _buf;
   EncryptStreamBuf _buf;
 };
 };

+ 18 - 0
dtool/src/prc/encryptStreamBuf.I

@@ -81,3 +81,21 @@ INLINE int EncryptStreamBuf::
 get_iteration_count() const {
 get_iteration_count() const {
   return _iteration_count;
   return _iteration_count;
 }
 }
+
+/**
+ * Sets the amount of the encrypted data at the beginning that are skipped
+ * when seeking back to zero.
+ */
+INLINE void EncryptStreamBuf::
+set_magic_length(size_t length) {
+  _magic_length = length;
+}
+
+/**
+ * Sets the amount of the encrypted data at the beginning that are skipped
+ * when seeking back to zero.
+ */
+INLINE size_t EncryptStreamBuf::
+get_magic_length() const {
+  return _magic_length;
+}

+ 54 - 3
dtool/src/prc/encryptStreamBuf.cxx

@@ -177,6 +177,7 @@ open_read(std::istream *source, bool owns_source, const std::string &password) {
 
 
   _read_overflow_buffer = new unsigned char[_read_block_size];
   _read_overflow_buffer = new unsigned char[_read_block_size];
   _in_read_overflow_buffer = 0;
   _in_read_overflow_buffer = 0;
+  _finished = false;
   thread_consider_yield();
   thread_consider_yield();
 }
 }
 
 
@@ -322,6 +323,57 @@ close_write() {
   }
   }
 }
 }
 
 
+/**
+ * Implements seeking within the stream.  EncryptStreamBuf only allows seeking
+ * back to the beginning of the stream.
+ */
+std::streampos EncryptStreamBuf::
+seekoff(std::streamoff off, ios_seekdir dir, ios_openmode which) {
+  if (which != std::ios::in) {
+    // We can only do this with the input stream.
+    return -1;
+  }
+
+  if (off != 0 || dir != std::ios::beg) {
+    // We only know how to reposition to the beginning.
+    return -1;
+  }
+
+  size_t n = egptr() - gptr();
+  gbump(n);
+
+  if (_source->rdbuf()->pubseekpos(0, std::ios::in) == (std::streampos)0) {
+    int result = EVP_DecryptInit(_read_ctx, nullptr, nullptr, nullptr);
+    nassertr_always(result > 0, -1);
+
+    _source->clear();
+    _in_read_overflow_buffer = 0;
+    _finished = false;
+
+    // Skip past the header.
+    int iv_length = EVP_CIPHER_CTX_iv_length(_read_ctx);
+    _source->ignore(6 + iv_length);
+
+    // Ignore the magic bytes.
+    size_t magic_length = get_magic_length();
+    char *buffer = (char *)alloca(magic_length);
+    if (read_chars(buffer, magic_length) == magic_length) {
+      return 0;
+    }
+  }
+
+  return -1;
+}
+
+/**
+ * Implements seeking within the stream.  EncryptStreamBuf only allows seeking
+ * back to the beginning of the stream.
+ */
+std::streampos EncryptStreamBuf::
+seekpos(std::streampos pos, ios_openmode which) {
+  return seekoff(pos, std::ios::beg, which);
+}
+
 /**
 /**
  * Called by the system ostream implementation when its internal buffer is
  * Called by the system ostream implementation when its internal buffer is
  * filled, plus one character.
  * filled, plus one character.
@@ -423,7 +475,7 @@ read_chars(char *start, size_t length) {
 
 
   do {
   do {
     // Get more bytes from the stream.
     // Get more bytes from the stream.
-    if (_read_ctx == nullptr) {
+    if (_read_ctx == nullptr || _finished) {
       return 0;
       return 0;
     }
     }
 
 
@@ -439,8 +491,7 @@ read_chars(char *start, size_t length) {
     } else {
     } else {
       result =
       result =
         EVP_DecryptFinal(_read_ctx, read_buffer, &bytes_read);
         EVP_DecryptFinal(_read_ctx, read_buffer, &bytes_read);
-      EVP_CIPHER_CTX_free(_read_ctx);
-      _read_ctx = nullptr;
+      _finished = true;
     }
     }
 
 
     if (result <= 0) {
     if (result <= 0) {

+ 9 - 0
dtool/src/prc/encryptStreamBuf.h

@@ -44,6 +44,12 @@ public:
   INLINE void set_iteration_count(int iteration_count);
   INLINE void set_iteration_count(int iteration_count);
   INLINE int get_iteration_count() const;
   INLINE int get_iteration_count() const;
 
 
+  INLINE void set_magic_length(size_t length);
+  INLINE size_t get_magic_length() const;
+
+  virtual std::streampos seekoff(std::streamoff off, ios_seekdir dir, ios_openmode which);
+  virtual std::streampos seekpos(std::streampos pos, ios_openmode which);
+
 protected:
 protected:
   virtual int overflow(int c);
   virtual int overflow(int c);
   virtual int sync();
   virtual int sync();
@@ -71,6 +77,9 @@ private:
 
 
   EVP_CIPHER_CTX *_write_ctx;
   EVP_CIPHER_CTX *_write_ctx;
   size_t _write_block_size;
   size_t _write_block_size;
+
+  size_t _magic_length = 0;
+  bool _finished = false;
 };
 };
 
 
 #include "encryptStreamBuf.I"
 #include "encryptStreamBuf.I"

+ 25 - 1
dtool/src/prc/streamReader.I

@@ -43,7 +43,18 @@ StreamReader(const StreamReader &copy) :
 }
 }
 
 
 /**
 /**
- * The copy constructor does not copy ownership of the stream.
+ * The move constructor steals ownership of the stream.
+ */
+INLINE StreamReader::
+StreamReader(StreamReader &&from) noexcept :
+  _in(from._in),
+  _owns_stream(from._owns_stream)
+{
+  from._owns_stream = false;
+}
+
+/**
+ * The copy assignment operator does not copy ownership of the stream.
  */
  */
 INLINE void StreamReader::
 INLINE void StreamReader::
 operator = (const StreamReader &copy) {
 operator = (const StreamReader &copy) {
@@ -54,6 +65,19 @@ operator = (const StreamReader &copy) {
   _owns_stream = false;
   _owns_stream = false;
 }
 }
 
 
+/**
+ * The move assignment operator steals ownership of the stream.
+ */
+INLINE void StreamReader::
+operator = (StreamReader &&from) noexcept {
+  if (_owns_stream) {
+    delete _in;
+  }
+  _in = from._in;
+  _owns_stream = from._owns_stream;
+  from._owns_stream = false;
+}
+
 /**
 /**
  *
  *
  */
  */

+ 2 - 0
dtool/src/prc/streamReader.h

@@ -31,7 +31,9 @@ public:
 PUBLISHED:
 PUBLISHED:
   INLINE explicit StreamReader(std::istream *in, bool owns_stream);
   INLINE explicit StreamReader(std::istream *in, bool owns_stream);
   INLINE StreamReader(const StreamReader &copy);
   INLINE StreamReader(const StreamReader &copy);
+  INLINE StreamReader(StreamReader &&from) noexcept;
   INLINE void operator = (const StreamReader &copy);
   INLINE void operator = (const StreamReader &copy);
+  INLINE void operator = (StreamReader &&from) noexcept;
   INLINE ~StreamReader();
   INLINE ~StreamReader();
 
 
   INLINE std::istream *get_istream() const;
   INLINE std::istream *get_istream() const;

+ 28 - 1
dtool/src/prc/streamWriter.I

@@ -51,7 +51,21 @@ StreamWriter(const StreamWriter &copy) :
 }
 }
 
 
 /**
 /**
- * The copy constructor does not copy ownership of the stream.
+ * The move constructor steals ownership of the stream.
+ */
+INLINE StreamWriter::
+StreamWriter(StreamWriter &&from) noexcept :
+#ifdef HAVE_PYTHON
+  softspace(0),
+#endif
+  _out(from._out),
+  _owns_stream(from._owns_stream)
+{
+  from._owns_stream = false;
+}
+
+/**
+ * The copy assignment operator does not copy ownership of the stream.
  */
  */
 INLINE void StreamWriter::
 INLINE void StreamWriter::
 operator = (const StreamWriter &copy) {
 operator = (const StreamWriter &copy) {
@@ -62,6 +76,19 @@ operator = (const StreamWriter &copy) {
   _owns_stream = false;
   _owns_stream = false;
 }
 }
 
 
+/**
+ * The move assignment operator steals ownership of the stream.
+ */
+INLINE void StreamWriter::
+operator = (StreamWriter &&from) noexcept {
+  if (_owns_stream) {
+    delete _out;
+  }
+  _out = from._out;
+  _owns_stream = from._owns_stream;
+  from._owns_stream = false;
+}
+
 /**
 /**
  *
  *
  */
  */

+ 2 - 0
dtool/src/prc/streamWriter.h

@@ -32,7 +32,9 @@ public:
 PUBLISHED:
 PUBLISHED:
   INLINE explicit StreamWriter(std::ostream *out, bool owns_stream);
   INLINE explicit StreamWriter(std::ostream *out, bool owns_stream);
   INLINE StreamWriter(const StreamWriter &copy);
   INLINE StreamWriter(const StreamWriter &copy);
+  INLINE StreamWriter(StreamWriter &&from) noexcept;
   INLINE void operator = (const StreamWriter &copy);
   INLINE void operator = (const StreamWriter &copy);
+  INLINE void operator = (StreamWriter &&from) noexcept;
   INLINE ~StreamWriter();
   INLINE ~StreamWriter();
 
 
   INLINE std::ostream *get_ostream() const;
   INLINE std::ostream *get_ostream() const;

+ 31 - 6
makepanda/makepanda.py

@@ -1009,12 +1009,26 @@ if (COMPILER=="GCC"):
 
 
     if GetTarget() == 'darwin':
     if GetTarget() == 'darwin':
         LibName("ALWAYS", "-framework AppKit")
         LibName("ALWAYS", "-framework AppKit")
+        LibName("IOKIT", "-framework IOKit")
+        LibName("QUARTZ", "-framework Quartz")
         LibName("AGL", "-framework AGL")
         LibName("AGL", "-framework AGL")
         LibName("CARBON", "-framework Carbon")
         LibName("CARBON", "-framework Carbon")
         LibName("COCOA", "-framework Cocoa")
         LibName("COCOA", "-framework Cocoa")
         # Fix for a bug in OSX Leopard:
         # Fix for a bug in OSX Leopard:
         LibName("GL", "-dylib_file /System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib:/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib")
         LibName("GL", "-dylib_file /System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib:/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib")
 
 
+        # Temporary exceptions to removal of this flag
+        if not PkgSkip("ROCKET"):
+            LibName("ROCKET", "-undefined dynamic_lookup")
+        if not PkgSkip("FFMPEG"):
+            LibName("FFMPEG", "-undefined dynamic_lookup")
+        if not PkgSkip("ASSIMP"):
+            LibName("ASSIMP", "-undefined dynamic_lookup")
+        if not PkgSkip("OPENEXR"):
+            LibName("OPENEXR", "-undefined dynamic_lookup")
+        if not PkgSkip("VRPN"):
+            LibName("VRPN", "-undefined dynamic_lookup")
+
     if GetTarget() == 'android':
     if GetTarget() == 'android':
         LibName("ALWAYS", '-llog')
         LibName("ALWAYS", '-llog')
         LibName("ANDROID", '-landroid')
         LibName("ANDROID", '-landroid')
@@ -1140,6 +1154,7 @@ def BracketNameWithQuotes(name):
     # Workaround for OSX bug - compiler doesn't like those flags quoted.
     # Workaround for OSX bug - compiler doesn't like those flags quoted.
     if (name.startswith("-framework")): return name
     if (name.startswith("-framework")): return name
     if (name.startswith("-dylib_file")): return name
     if (name.startswith("-dylib_file")): return name
+    if (name.startswith("-undefined ")): return name
 
 
     # Don't add quotes when it's not necessary.
     # Don't add quotes when it's not necessary.
     if " " not in name: return name
     if " " not in name: return name
@@ -1813,9 +1828,11 @@ def CompileLink(dll, obj, opts):
                 cmd += ' -Wl,--allow-shlib-undefined'
                 cmd += ' -Wl,--allow-shlib-undefined'
         else:
         else:
             if (GetTarget() == "darwin"):
             if (GetTarget() == "darwin"):
-                cmd = cxx + ' -undefined dynamic_lookup'
-                if ("BUNDLE" in opts or GetOrigExt(dll) == ".pyd"):
-                    cmd += ' -bundle '
+                cmd = cxx
+                if GetOrigExt(dll) == ".pyd":
+                    cmd += ' -bundle -undefined dynamic_lookup'
+                elif "BUNDLE" in opts:
+                    cmd += ' -bundle'
                 else:
                 else:
                     install_name = '@loader_path/../lib/' + os.path.basename(dll)
                     install_name = '@loader_path/../lib/' + os.path.basename(dll)
                     cmd += ' -dynamiclib -install_name ' + install_name
                     cmd += ' -dynamiclib -install_name ' + install_name
@@ -2325,7 +2342,6 @@ DTOOL_CONFIG=[
     ("COMPILE_IN_DEFAULT_FONT",        '1',                      '1'),
     ("COMPILE_IN_DEFAULT_FONT",        '1',                      '1'),
     ("STDFLOAT_DOUBLE",                'UNDEF',                  'UNDEF'),
     ("STDFLOAT_DOUBLE",                'UNDEF',                  'UNDEF'),
     ("HAVE_MAYA",                      '1',                      '1'),
     ("HAVE_MAYA",                      '1',                      '1'),
-    ("HAVE_SOFTIMAGE",                 'UNDEF',                  'UNDEF'),
     ("REPORT_OPENSSL_ERRORS",          '1',                      '1'),
     ("REPORT_OPENSSL_ERRORS",          '1',                      '1'),
     ("USE_PANDAFILESTREAM",            '1',                      '1'),
     ("USE_PANDAFILESTREAM",            '1',                      '1'),
     ("USE_DELETED_CHAIN",              '1',                      '1'),
     ("USE_DELETED_CHAIN",              '1',                      '1'),
@@ -2866,6 +2882,14 @@ for basename in del_files:
 p3d_init = """"Python bindings for the Panda3D libraries"
 p3d_init = """"Python bindings for the Panda3D libraries"
 
 
 __version__ = '%s'
 __version__ = '%s'
+
+if __debug__:
+    import sys
+    if sys.version_info < (3, 0):
+        sys.stderr.write("WARNING: Python 2.7 will reach EOL after December 31, 2019.\\n")
+        sys.stderr.write("To suppress this warning, upgrade to Python 3.\\n")
+        sys.stderr.flush()
+    del sys
 """ % (WHLVERSION)
 """ % (WHLVERSION)
 
 
 if GetTarget() == 'windows':
 if GetTarget() == 'windows':
@@ -3661,6 +3685,7 @@ IGATEFILES += [
     "globPattern_ext.h",
     "globPattern_ext.h",
     "pandaFileStream.h",
     "pandaFileStream.h",
     "lineStream.h",
     "lineStream.h",
+    "iostream_ext.h",
 ]
 ]
 TargetAdd('libp3dtoolutil.in', opts=OPTS, input=IGATEFILES)
 TargetAdd('libp3dtoolutil.in', opts=OPTS, input=IGATEFILES)
 TargetAdd('libp3dtoolutil.in', opts=['IMOD:panda3d.core', 'ILIB:libp3dtoolutil', 'SRCDIR:dtool/src/dtoolutil'])
 TargetAdd('libp3dtoolutil.in', opts=['IMOD:panda3d.core', 'ILIB:libp3dtoolutil', 'SRCDIR:dtool/src/dtoolutil'])
@@ -4204,7 +4229,7 @@ if (not RUNTIME):
   OPTS=['DIR:panda/metalibs/panda', 'BUILDING:PANDA', 'JPEG', 'PNG', 'HARFBUZZ',
   OPTS=['DIR:panda/metalibs/panda', 'BUILDING:PANDA', 'JPEG', 'PNG', 'HARFBUZZ',
       'TIFF', 'OPENEXR', 'ZLIB', 'OPENSSL', 'FREETYPE', 'FFTW', 'ADVAPI', 'WINSOCK2',
       'TIFF', 'OPENEXR', 'ZLIB', 'OPENSSL', 'FREETYPE', 'FFTW', 'ADVAPI', 'WINSOCK2',
       'SQUISH', 'NVIDIACG', 'VORBIS', 'OPUS', 'WINUSER', 'WINMM', 'WINGDI', 'IPHLPAPI',
       'SQUISH', 'NVIDIACG', 'VORBIS', 'OPUS', 'WINUSER', 'WINMM', 'WINGDI', 'IPHLPAPI',
-      'SETUPAPI']
+      'SETUPAPI', 'IOKIT']
 
 
   TargetAdd('panda_panda.obj', opts=OPTS, input='panda.cxx')
   TargetAdd('panda_panda.obj', opts=OPTS, input='panda.cxx')
 
 
@@ -4843,7 +4868,7 @@ if (GetTarget() == 'darwin' and PkgSkip("COCOA")==0 and PkgSkip("GL")==0 and not
   if (PkgSkip('PANDAFX')==0):
   if (PkgSkip('PANDAFX')==0):
     TargetAdd('libpandagl.dll', input='libpandafx.dll')
     TargetAdd('libpandagl.dll', input='libpandafx.dll')
   TargetAdd('libpandagl.dll', input=COMMON_PANDA_LIBS)
   TargetAdd('libpandagl.dll', input=COMMON_PANDA_LIBS)
-  TargetAdd('libpandagl.dll', opts=['MODULE', 'GL', 'NVIDIACG', 'CGGL', 'COCOA', 'CARBON'])
+  TargetAdd('libpandagl.dll', opts=['MODULE', 'GL', 'NVIDIACG', 'CGGL', 'COCOA', 'CARBON', 'QUARTZ'])
 
 
 #
 #
 # DIRECTORY: panda/src/wgldisplay/
 # DIRECTORY: panda/src/wgldisplay/

+ 15 - 2
makepanda/makewheel.py

@@ -596,10 +596,23 @@ def makewheel(version, output_dir, platform=None):
 
 
     # Write the panda3d tree.  We use a custom empty __init__ since the
     # Write the panda3d tree.  We use a custom empty __init__ since the
     # default one adds the bin directory to the PATH, which we don't have.
     # default one adds the bin directory to the PATH, which we don't have.
-    whl.write_file_data('panda3d/__init__.py', """"Python bindings for the Panda3D libraries"
+    p3d_init = """"Python bindings for the Panda3D libraries"
 
 
 __version__ = '{0}'
 __version__ = '{0}'
-""".format(version))
+""".format(version)
+
+    if '27' in ABI_TAG:
+        p3d_init += """
+if __debug__:
+    import sys
+    if sys.version_info < (3, 0):
+        sys.stderr.write("WARNING: Python 2.7 will reach EOL after December 31, 2019.\\n")
+        sys.stderr.write("To suppress this warning, upgrade to Python 3.\\n")
+        sys.stderr.flush()
+    del sys
+"""
+
+    whl.write_file_data('panda3d/__init__.py', p3d_init)
 
 
     # Copy the extension modules from the panda3d directory.
     # Copy the extension modules from the panda3d directory.
     ext_suffix = GetExtensionSuffix()
     ext_suffix = GetExtensionSuffix()

+ 16 - 13
makepanda/test_wheel.py

@@ -17,31 +17,34 @@ from optparse import OptionParser
 def test_wheel(wheel, verbose=False):
 def test_wheel(wheel, verbose=False):
     envdir = tempfile.mkdtemp(prefix="venv-")
     envdir = tempfile.mkdtemp(prefix="venv-")
     print("Setting up virtual environment in {0}".format(envdir))
     print("Setting up virtual environment in {0}".format(envdir))
+    sys.stdout.flush()
 
 
+    # Make sure pip is up-to-date first.
+    subprocess.call([sys.executable, "-B", "-m", "pip", "install", "-U", "pip"])
+
+    # Create a virtualenv.
     if sys.version_info >= (3, 0):
     if sys.version_info >= (3, 0):
-        subprocess.call([sys.executable, "-m", "venv", "--clear", envdir])
+        subprocess.call([sys.executable, "-B", "-m", "venv", "--clear", envdir])
     else:
     else:
-        subprocess.call([sys.executable, "-m", "virtualenv", "--clear", envdir])
+        subprocess.call([sys.executable, "-B", "-m", "virtualenv", "--clear", envdir])
 
 
-    # Make sure pip is up-to-date first.
-    if subprocess.call([sys.executable, "-m", "pip", "install", "-U", "pip"]) != 0:
+    # Determine the path to the Python interpreter.
+    if sys.platform == "win32":
+        python = os.path.join(envdir, "Scripts", "python.exe")
+    else:
+        python = os.path.join(envdir, "bin", "python")
+
+    # Upgrade pip inside the environment too.
+    if subprocess.call([python, "-m", "pip", "install", "-U", "pip"]) != 0:
         shutil.rmtree(envdir)
         shutil.rmtree(envdir)
         sys.exit(1)
         sys.exit(1)
 
 
     # Install pytest into the environment, as well as our wheel.
     # Install pytest into the environment, as well as our wheel.
-    if sys.platform == "win32":
-        pip = os.path.join(envdir, "Scripts", "pip.exe")
-    else:
-        pip = os.path.join(envdir, "bin", "pip")
-    if subprocess.call([pip, "install", "pytest", wheel]) != 0:
+    if subprocess.call([python, "-m", "pip", "install", "pytest", wheel]) != 0:
         shutil.rmtree(envdir)
         shutil.rmtree(envdir)
         sys.exit(1)
         sys.exit(1)
 
 
     # Run the test suite.
     # Run the test suite.
-    if sys.platform == "win32":
-        python = os.path.join(envdir, "Scripts", "python.exe")
-    else:
-        python = os.path.join(envdir, "bin", "python")
     test_cmd = [python, "-m", "pytest", "tests"]
     test_cmd = [python, "-m", "pytest", "tests"]
     if verbose:
     if verbose:
         test_cmd.append("--verbose")
         test_cmd.append("--verbose")

+ 1 - 1
panda/src/audiotraits/openalAudioManager.cxx

@@ -442,7 +442,7 @@ get_sound_data(MovieAudio *movie, int mode) {
     int channels = stream->audio_channels();
     int channels = stream->audio_channels();
     int samples = (int)(stream->length() * stream->audio_rate());
     int samples = (int)(stream->length() * stream->audio_rate());
     int16_t *data = new int16_t[samples * channels];
     int16_t *data = new int16_t[samples * channels];
-    stream->read_samples(samples, data);
+    samples = stream->read_samples(samples, data);
     alBufferData(sd->_sample,
     alBufferData(sd->_sample,
                  (channels>1) ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16,
                  (channels>1) ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16,
                  data, samples * channels * 2, stream->audio_rate());
                  data, samples * channels * 2, stream->audio_rate());

+ 15 - 5
panda/src/audiotraits/openalAudioSound.cxx

@@ -373,7 +373,6 @@ read_stream_data(int bytelen, unsigned char *buffer) {
   nassertr(has_sound_data(), 0);
   nassertr(has_sound_data(), 0);
 
 
   MovieAudioCursor *cursor = _sd->_stream;
   MovieAudioCursor *cursor = _sd->_stream;
-  double length = cursor->length();
   int channels = cursor->audio_channels();
   int channels = cursor->audio_channels();
   int rate = cursor->audio_rate();
   int rate = cursor->audio_rate();
   int space = bytelen / (channels * 2);
   int space = bytelen / (channels * 2);
@@ -381,7 +380,7 @@ read_stream_data(int bytelen, unsigned char *buffer) {
 
 
   while (space && (_loops_completed < _playing_loops)) {
   while (space && (_loops_completed < _playing_loops)) {
     double t = cursor->tell();
     double t = cursor->tell();
-    double remain = length - t;
+    double remain = cursor->length() - t;
     if (remain > 60.0) {
     if (remain > 60.0) {
       remain = 60.0;
       remain = 60.0;
     }
     }
@@ -403,9 +402,20 @@ read_stream_data(int bytelen, unsigned char *buffer) {
     if (samples > _sd->_stream->ready()) {
     if (samples > _sd->_stream->ready()) {
       samples = _sd->_stream->ready();
       samples = _sd->_stream->ready();
     }
     }
-    cursor->read_samples(samples, (int16_t *)buffer);
-    size_t hval = AddHash::add_hash(0, (uint8_t*)buffer, samples*channels*2);
-    audio_debug("Streaming " << cursor->get_source()->get_name() << " at " << t << " hash " << hval);
+    samples = cursor->read_samples(samples, (int16_t *)buffer);
+    if (audio_cat.is_debug()) {
+      size_t hval = AddHash::add_hash(0, (uint8_t*)buffer, samples*channels*2);
+      audio_debug("Streaming " << cursor->get_source()->get_name() << " at " << t << " hash " << hval);
+    }
+    if (samples == 0) {
+      _loops_completed += 1;
+      cursor->seek(0.0);
+      if (_playing_loops >= 1000000000) {
+        // Prevent infinite loop if endlessly looping empty sound
+        return fill;
+      }
+      continue;
+    }
     fill += samples;
     fill += samples;
     space -= samples;
     space -= samples;
     buffer += (samples * channels * 2);
     buffer += (samples * channels * 2);

+ 14 - 0
panda/src/bullet/bulletRigidBodyNode.cxx

@@ -359,6 +359,20 @@ do_transform_changed() {
   }
   }
 }
 }
 
 
+/**
+ *
+ */
+void BulletRigidBodyNode::
+parents_changed() {
+
+  if (_motion.sync_disabled()) return;
+
+  if (get_num_parents() > 0) {
+    LightMutexHolder holder(BulletWorld::get_global_lock());
+    do_transform_changed();
+  }
+}
+
 /**
 /**
  *
  *
  */
  */

+ 1 - 0
panda/src/bullet/bulletRigidBodyNode.h

@@ -112,6 +112,7 @@ public:
   void do_sync_b2p();
   void do_sync_b2p();
 
 
 protected:
 protected:
+  virtual void parents_changed();
   virtual void transform_changed();
   virtual void transform_changed();
 
 
 private:
 private:

+ 1 - 1
panda/src/cocoadisplay/cocoaGraphicsBuffer.h

@@ -21,7 +21,7 @@
  * This is a light wrapper around GLGraphicsBuffer (ie. FBOs) to interface
  * This is a light wrapper around GLGraphicsBuffer (ie. FBOs) to interface
  * with Cocoa contexts, so that it can be used without a host window.
  * with Cocoa contexts, so that it can be used without a host window.
  */
  */
-class CocoaGraphicsBuffer : public GLGraphicsBuffer {
+class EXPCL_PANDA_COCOADISPLAY CocoaGraphicsBuffer : public GLGraphicsBuffer {
 public:
 public:
   CocoaGraphicsBuffer(GraphicsEngine *engine, GraphicsPipe *pipe,
   CocoaGraphicsBuffer(GraphicsEngine *engine, GraphicsPipe *pipe,
                       const std::string &name,
                       const std::string &name,

+ 1 - 1
panda/src/cocoadisplay/cocoaGraphicsPipe.h

@@ -33,7 +33,7 @@ class FrameBufferProperties;
  * This graphics pipe represents the interface for creating OpenGL graphics
  * This graphics pipe represents the interface for creating OpenGL graphics
  * windows on a Cocoa-based (e.g.  Mac OS X) client.
  * windows on a Cocoa-based (e.g.  Mac OS X) client.
  */
  */
-class CocoaGraphicsPipe : public GraphicsPipe {
+class EXPCL_PANDA_COCOADISPLAY CocoaGraphicsPipe : public GraphicsPipe {
 public:
 public:
   CocoaGraphicsPipe(CGDirectDisplayID display = CGMainDisplayID());
   CocoaGraphicsPipe(CGDirectDisplayID display = CGMainDisplayID());
   virtual ~CocoaGraphicsPipe();
   virtual ~CocoaGraphicsPipe();

+ 1 - 1
panda/src/cocoadisplay/cocoaGraphicsStateGuardian.h

@@ -26,7 +26,7 @@
  * A tiny specialization on GLGraphicsStateGuardian to add some Cocoa-specific
  * A tiny specialization on GLGraphicsStateGuardian to add some Cocoa-specific
  * information.
  * information.
  */
  */
-class CocoaGraphicsStateGuardian : public GLGraphicsStateGuardian {
+class EXPCL_PANDA_COCOADISPLAY CocoaGraphicsStateGuardian : public GLGraphicsStateGuardian {
 public:
 public:
   INLINE const FrameBufferProperties &get_fb_properties() const;
   INLINE const FrameBufferProperties &get_fb_properties() const;
   void get_properties(FrameBufferProperties &properties,
   void get_properties(FrameBufferProperties &properties,

+ 1 - 1
panda/src/cocoadisplay/cocoaGraphicsWindow.h

@@ -29,7 +29,7 @@
  * An interface to the Cocoa system for managing OpenGL windows under Mac OS
  * An interface to the Cocoa system for managing OpenGL windows under Mac OS
  * X.
  * X.
  */
  */
-class CocoaGraphicsWindow : public GraphicsWindow {
+class EXPCL_PANDA_COCOADISPLAY CocoaGraphicsWindow : public GraphicsWindow {
 public:
 public:
   CocoaGraphicsWindow(GraphicsEngine *engine, GraphicsPipe *pipe,
   CocoaGraphicsWindow(GraphicsEngine *engine, GraphicsPipe *pipe,
                       const std::string &name,
                       const std::string &name,

+ 2 - 2
panda/src/cocoadisplay/cocoaGraphicsWindow.mm

@@ -1618,7 +1618,7 @@ handle_key_event(NSEvent *event) {
   if ([event type] == NSKeyDown) {
   if ([event type] == NSKeyDown) {
     // Translate it to a unicode character for keystrokes.  I would use
     // Translate it to a unicode character for keystrokes.  I would use
     // interpretKeyEvents and insertText, but that doesn't handle dead keys.
     // interpretKeyEvents and insertText, but that doesn't handle dead keys.
-    TISInputSourceRef input_source = TISCopyCurrentKeyboardInputSource();
+    TISInputSourceRef input_source = TISCopyCurrentKeyboardLayoutInputSource();
     CFDataRef layout_data = (CFDataRef)TISGetInputSourceProperty(input_source, kTISPropertyUnicodeKeyLayoutData);
     CFDataRef layout_data = (CFDataRef)TISGetInputSourceProperty(input_source, kTISPropertyUnicodeKeyLayoutData);
     const UCKeyboardLayout *layout = (const UCKeyboardLayout *)CFDataGetBytePtr(layout_data);
     const UCKeyboardLayout *layout = (const UCKeyboardLayout *)CFDataGetBytePtr(layout_data);
 
 
@@ -1827,7 +1827,7 @@ get_keyboard_map() const {
   const UCKeyboardLayout *layout;
   const UCKeyboardLayout *layout;
 
 
   // Get the current keyboard layout data.
   // Get the current keyboard layout data.
-  input_source = TISCopyCurrentKeyboardInputSource();
+  input_source = TISCopyCurrentKeyboardLayoutInputSource();
   layout_data = (CFDataRef) TISGetInputSourceProperty(input_source, kTISPropertyUnicodeKeyLayoutData);
   layout_data = (CFDataRef) TISGetInputSourceProperty(input_source, kTISPropertyUnicodeKeyLayoutData);
   layout = (const UCKeyboardLayout *)CFDataGetBytePtr(layout_data);
   layout = (const UCKeyboardLayout *)CFDataGetBytePtr(layout_data);
 
 

+ 1 - 1
panda/src/collide/collisionTraverser.h

@@ -49,7 +49,7 @@ PUBLISHED:
 
 
   INLINE void set_respect_prev_transform(bool flag);
   INLINE void set_respect_prev_transform(bool flag);
   INLINE bool get_respect_prev_transform() const;
   INLINE bool get_respect_prev_transform() const;
-  MAKE_PROPERTY(respect_preV_transform, get_respect_prev_transform,
+  MAKE_PROPERTY(respect_prev_transform, get_respect_prev_transform,
                                         set_respect_prev_transform);
                                         set_respect_prev_transform);
 
 
   void add_collider(const NodePath &collider, CollisionHandler *handler);
   void add_collider(const NodePath &collider, CollisionHandler *handler);

+ 61 - 11
panda/src/device/evdevInputDevice.cxx

@@ -62,6 +62,15 @@ enum QuirkBits {
   // We only connect it if it is reporting any events, because when Steam is
   // We only connect it if it is reporting any events, because when Steam is
   // running, the Steam controller is muted in favour of a dummy Xbox device.
   // running, the Steam controller is muted in favour of a dummy Xbox device.
   QB_steam_controller = 32,
   QB_steam_controller = 32,
+
+  // Axes on the right stick are swapped, using x for y and vice versa.
+  QB_right_axes_swapped = 64,
+
+  // Has no trigger axes.
+  QB_no_analog_triggers = 128,
+
+  // Alternate button mapping.
+  QB_alt_button_mapping = 256,
 };
 };
 
 
 static const struct DeviceMapping {
 static const struct DeviceMapping {
@@ -81,11 +90,15 @@ static const struct DeviceMapping {
   // Steam Controller (wireless)
   // Steam Controller (wireless)
   {0x28de, 0x1142, InputDevice::DeviceClass::unknown, QB_steam_controller},
   {0x28de, 0x1142, InputDevice::DeviceClass::unknown, QB_steam_controller},
   // Jess Tech Colour Rumble Pad
   // Jess Tech Colour Rumble Pad
-  {0x0f30, 0x0111, InputDevice::DeviceClass::gamepad, 0},
-  // SPEED Link SL-6535-SBK-01
-  {0x0079, 0x0006, InputDevice::DeviceClass::gamepad, 0},
+  {0x0f30, 0x0111, InputDevice::DeviceClass::gamepad, QB_rstick_from_z | QB_right_axes_swapped},
+  // Trust GXT 24
+  {0x0079, 0x0006, InputDevice::DeviceClass::gamepad, QB_no_analog_triggers | QB_alt_button_mapping},
   // 8bitdo N30 Pro Controller
   // 8bitdo N30 Pro Controller
   {0x2dc8, 0x9001, InputDevice::DeviceClass::gamepad, QB_rstick_from_z},
   {0x2dc8, 0x9001, InputDevice::DeviceClass::gamepad, QB_rstick_from_z},
+  // Generic gamepad
+  {0x0810, 0x0001, InputDevice::DeviceClass::gamepad, QB_no_analog_triggers | QB_alt_button_mapping | QB_rstick_from_z | QB_right_axes_swapped},
+  // Generic gamepad without sticks
+  {0x0810, 0xe501, InputDevice::DeviceClass::gamepad, QB_no_analog_triggers | QB_alt_button_mapping},
   // 3Dconnexion Space Traveller 3D Mouse
   // 3Dconnexion Space Traveller 3D Mouse
   {0x046d, 0xc623, InputDevice::DeviceClass::spatial_mouse, 0},
   {0x046d, 0xc623, InputDevice::DeviceClass::spatial_mouse, 0},
   // 3Dconnexion Space Pilot 3D Mouse
   // 3Dconnexion Space Pilot 3D Mouse
@@ -488,10 +501,16 @@ init_device() {
           break;
           break;
         case ABS_Z:
         case ABS_Z:
           if (quirks & QB_rstick_from_z) {
           if (quirks & QB_rstick_from_z) {
-            axis = InputDevice::Axis::right_x;
+            if (quirks & QB_right_axes_swapped) {
+              axis = InputDevice::Axis::right_y;
+            } else {
+              axis = InputDevice::Axis::right_x;
+            }
           } else if (_device_class == DeviceClass::gamepad) {
           } else if (_device_class == DeviceClass::gamepad) {
-            axis = InputDevice::Axis::left_trigger;
-            have_analog_triggers = true;
+            if ((quirks & QB_no_analog_triggers) == 0) {
+              axis = InputDevice::Axis::left_trigger;
+              have_analog_triggers = true;
+            }
           } else if (_device_class == DeviceClass::spatial_mouse) {
           } else if (_device_class == DeviceClass::spatial_mouse) {
             axis = InputDevice::Axis::z;
             axis = InputDevice::Axis::z;
           } else {
           } else {
@@ -514,10 +533,19 @@ init_device() {
           break;
           break;
         case ABS_RZ:
         case ABS_RZ:
           if (quirks & QB_rstick_from_z) {
           if (quirks & QB_rstick_from_z) {
-            axis = InputDevice::Axis::right_y;
+            if (quirks & QB_right_axes_swapped) {
+              axis = InputDevice::Axis::right_x;
+            } else {
+              axis = InputDevice::Axis::right_y;
+            }
           } else if (_device_class == DeviceClass::gamepad) {
           } else if (_device_class == DeviceClass::gamepad) {
-            axis = InputDevice::Axis::right_trigger;
-            have_analog_triggers = true;
+            if ((quirks & QB_no_analog_triggers) == 0) {
+              axis = InputDevice::Axis::right_trigger;
+              have_analog_triggers = true;
+            } else {
+              // Special weird case for Trust GXT 24
+              axis = InputDevice::Axis::right_y;
+            }
           } else {
           } else {
             axis = InputDevice::Axis::yaw;
             axis = InputDevice::Axis::yaw;
           }
           }
@@ -537,8 +565,10 @@ init_device() {
           break;
           break;
         case ABS_GAS:
         case ABS_GAS:
           if (_device_class == DeviceClass::gamepad) {
           if (_device_class == DeviceClass::gamepad) {
-            axis = InputDevice::Axis::right_trigger;
-            have_analog_triggers = true;
+            if ((quirks & QB_no_analog_triggers) == 0) {
+              axis = InputDevice::Axis::right_trigger;
+              have_analog_triggers = true;
+            }
           } else {
           } else {
             axis = InputDevice::Axis::accelerator;
             axis = InputDevice::Axis::accelerator;
           }
           }
@@ -963,6 +993,26 @@ map_button(int code, DeviceClass device_class, int quirks) {
       // BTN_THUMB and BTN_THUMB2 detect touching the touchpads.
       // BTN_THUMB and BTN_THUMB2 detect touching the touchpads.
       return ButtonHandle::none();
       return ButtonHandle::none();
 
 
+    } else if (device_class == DeviceClass::gamepad &&
+               (quirks & QB_alt_button_mapping) != 0) {
+      static const ButtonHandle mapping[] = {
+        GamepadButton::face_y(),
+        GamepadButton::face_b(),
+        GamepadButton::face_a(),
+        GamepadButton::face_x(),
+        GamepadButton::lshoulder(),
+        GamepadButton::rshoulder(),
+        GamepadButton::ltrigger(),
+        GamepadButton::rtrigger(),
+        GamepadButton::back(),
+        GamepadButton::start(),
+        GamepadButton::lstick(),
+        GamepadButton::rstick(),
+      };
+      if ((code & 0xf) < 12) {
+        return mapping[code & 0xf];
+      }
+
     } else if (device_class == DeviceClass::gamepad) {
     } else if (device_class == DeviceClass::gamepad) {
       // Based on "Jess Tech Colour Rumble Pad"
       // Based on "Jess Tech Colour Rumble Pad"
       static const ButtonHandle mapping[] = {
       static const ButtonHandle mapping[] = {

+ 4 - 1
panda/src/device/ioKitInputDevice.cxx

@@ -22,8 +22,11 @@
 #include "mouseButton.h"
 #include "mouseButton.h"
 
 
 static void removal_callback(void *ctx, IOReturn result, void *sender) {
 static void removal_callback(void *ctx, IOReturn result, void *sender) {
-  IOKitInputDevice *input_device = (IOKitInputDevice *)ctx;
+  // We need to hold a reference to this because it may otherwise be destroyed
+  // during the call to on_remove().
+  PT(IOKitInputDevice) input_device = (IOKitInputDevice *)ctx;
   nassertv(input_device != nullptr);
   nassertv(input_device != nullptr);
+  nassertv(input_device->test_ref_count_integrity());
   input_device->on_remove();
   input_device->on_remove();
 }
 }
 
 

+ 3 - 0
panda/src/device/linuxInputDeviceManager.cxx

@@ -61,6 +61,9 @@ LinuxInputDeviceManager() {
 
 
     // We'll want to sort the devices by index, since the order may be
     // We'll want to sort the devices by index, since the order may be
     // meaningful (eg. for the Xbox wireless receiver).
     // meaningful (eg. for the Xbox wireless receiver).
+    if (indices.empty()) {
+      return;
+    }
     std::sort(indices.begin(), indices.end());
     std::sort(indices.begin(), indices.end());
     _evdev_devices.resize(indices.back() + 1, nullptr);
     _evdev_devices.resize(indices.back() + 1, nullptr);
 
 

+ 42 - 7
panda/src/device/winRawInputDevice.cxx

@@ -32,6 +32,12 @@ enum QuirkBits : int {
 
 
   // Throttle is reversed.
   // Throttle is reversed.
   QB_reversed_throttle = 4,
   QB_reversed_throttle = 4,
+
+  // Right stick uses Z and Rz inputs.
+  QB_rstick_from_z = 8,
+
+  // Axes on the right stick are swapped, using x for y and vice versa.
+  QB_right_axes_swapped = 64,
 };
 };
 
 
 // Some nonstandard gamepads have different button mappings.
 // Some nonstandard gamepads have different button mappings.
@@ -42,12 +48,17 @@ static const struct DeviceMapping {
   int quirks;
   int quirks;
   const char *buttons[16];
   const char *buttons[16];
 } mapping_presets[] = {
 } mapping_presets[] = {
-  // SNES-style USB gamepad
+  // SNES-style USB gamepad, or cheap unbranded USB gamepad with no sticks
+  // ABXY are mapped based on their position, not based on their label.
   {0x0810, 0xe501, InputDevice::DeviceClass::gamepad, QB_no_analog_triggers,
   {0x0810, 0xe501, InputDevice::DeviceClass::gamepad, QB_no_analog_triggers,
-    {"face_x", "face_a", "face_b", "face_y", "lshoulder", "rshoulder", "none", "none", "back", "start"}
+    {"face_y", "face_b", "face_a", "face_x", "lshoulder", "rshoulder", "ltrigger", "rtrigger", "back", "start"}
+  },
+  // Unbranded generic cheap USB gamepad
+  {0x0810, 0x0001, InputDevice::DeviceClass::gamepad, QB_rstick_from_z | QB_no_analog_triggers | QB_right_axes_swapped,
+    {"face_y", "face_b", "face_a", "face_x", "lshoulder", "rshoulder", "ltrigger", "rtrigger", "back", "start", "lstick", "rstick"}
   },
   },
-  // SPEED Link SL-6535-SBK-01
-  {0x0079, 0x0006, InputDevice::DeviceClass::gamepad, QB_no_analog_triggers,
+  // Trust GXT 24 / SPEED Link SL-6535-SBK-01
+  {0x0079, 0x0006, InputDevice::DeviceClass::gamepad, QB_rstick_from_z | QB_no_analog_triggers,
     {"face_y", "face_b", "face_a", "face_x", "lshoulder", "rshoulder", "ltrigger", "rtrigger", "back", "start", "lstick", "rstick"}
     {"face_y", "face_b", "face_a", "face_x", "lshoulder", "rshoulder", "ltrigger", "rtrigger", "back", "start", "lstick", "rstick"}
   },
   },
   // T.Flight Hotas X
   // T.Flight Hotas X
@@ -56,7 +67,7 @@ static const struct DeviceMapping {
   },
   },
   // NVIDIA Shield Controller
   // NVIDIA Shield Controller
   {0x0955, 0x7214, InputDevice::DeviceClass::gamepad, 0,
   {0x0955, 0x7214, InputDevice::DeviceClass::gamepad, 0,
-    {"face_a", "face_b", "n", "face_x", "face_y", "rshoulder", "lshoulder", "rshoulder", "e", "f", "g", "start", "h", "lstick", "rstick", "i"}
+    {"face_a", "face_b", 0, "face_x", "face_y", "rshoulder", "lshoulder", "rshoulder", 0, 0, 0, "start", 0, "lstick", "rstick", 0}
   },
   },
   {0},
   {0},
 };
 };
@@ -422,7 +433,14 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) {
           break;
           break;
         case HID_USAGE_GENERIC_Z:
         case HID_USAGE_GENERIC_Z:
           if (_device_class == DeviceClass::gamepad) {
           if (_device_class == DeviceClass::gamepad) {
-            if ((quirks & QB_no_analog_triggers) == 0) {
+            if (quirks & QB_rstick_from_z) {
+              if (quirks & QB_right_axes_swapped) {
+                axis = InputDevice::Axis::right_y;
+                swap(cap.LogicalMin, cap.LogicalMax);
+              } else {
+                axis = InputDevice::Axis::right_x;
+              }
+            } else if ((quirks & QB_no_analog_triggers) == 0) {
               axis = Axis::left_trigger;
               axis = Axis::left_trigger;
             }
             }
           } else if (_device_class == DeviceClass::flight_stick) {
           } else if (_device_class == DeviceClass::flight_stick) {
@@ -455,7 +473,14 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) {
           break;
           break;
         case HID_USAGE_GENERIC_RZ:
         case HID_USAGE_GENERIC_RZ:
           if (_device_class == DeviceClass::gamepad) {
           if (_device_class == DeviceClass::gamepad) {
-            if ((quirks & QB_no_analog_triggers) == 0) {
+            if (quirks & QB_rstick_from_z) {
+              if (quirks & QB_right_axes_swapped) {
+                axis = InputDevice::Axis::right_x;
+              } else {
+                axis = InputDevice::Axis::right_y;
+                swap(cap.LogicalMin, cap.LogicalMax);
+              }
+            } else if ((quirks & QB_no_analog_triggers) == 0) {
               axis = Axis::right_trigger;
               axis = Axis::right_trigger;
             }
             }
           } else {
           } else {
@@ -481,6 +506,16 @@ on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) {
         break;
         break;
       }
       }
 
 
+      // If this axis already exists, don't double-map it, but take the first
+      // one.  This is important for the Trust GXT 24 / SL-6535-SBK-01 which
+      // have a weird extra Z axis with DataIndex 2 that should be ignored.
+      for (size_t i = 0; i < _axes.size(); ++i) {
+        if (_axes[i].axis == axis) {
+          axis = Axis::none;
+          break;
+        }
+      }
+
       int axis_index;
       int axis_index;
       if (!is_signed) {
       if (!is_signed) {
         // All axes on the weird XInput-style mappings go from -1 to 1
         // All axes on the weird XInput-style mappings go from -1 to 1

+ 8 - 0
panda/src/display/graphicsStateGuardian.cxx

@@ -1901,6 +1901,14 @@ fetch_ptr_parameter(const Shader::ShaderPtrSpec& spec) {
   return (_target_shader->get_shader_input_ptr(spec._arg));
   return (_target_shader->get_shader_input_ptr(spec._arg));
 }
 }
 
 
+/**
+ *
+ */
+bool GraphicsStateGuardian::
+fetch_ptr_parameter(const Shader::ShaderPtrSpec& spec, Shader::ShaderPtrData &data) {
+  return _target_shader->get_shader_input_ptr(spec._arg, data);
+}
+
 /**
 /**
  * Makes the specified DisplayRegion current.  All future drawing and clear
  * Makes the specified DisplayRegion current.  All future drawing and clear
  * operations will be constrained within the given DisplayRegion.
  * operations will be constrained within the given DisplayRegion.

+ 1 - 0
panda/src/display/graphicsStateGuardian.h

@@ -339,6 +339,7 @@ public:
   PT(Texture) fetch_specified_texture(Shader::ShaderTexSpec &spec,
   PT(Texture) fetch_specified_texture(Shader::ShaderTexSpec &spec,
                                       SamplerState &sampler, int &view);
                                       SamplerState &sampler, int &view);
   const Shader::ShaderPtrData *fetch_ptr_parameter(const Shader::ShaderPtrSpec& spec);
   const Shader::ShaderPtrData *fetch_ptr_parameter(const Shader::ShaderPtrSpec& spec);
+  bool fetch_ptr_parameter(const Shader::ShaderPtrSpec &spec, Shader::ShaderPtrData &data);
 
 
   virtual void prepare_display_region(DisplayRegionPipelineReader *dr);
   virtual void prepare_display_region(DisplayRegionPipelineReader *dr);
   virtual void clear_before_callback();
   virtual void clear_before_callback();

+ 1 - 4
panda/src/express/multifile.cxx

@@ -2068,10 +2068,7 @@ open_read_subfile(Subfile *subfile) {
     stream = wrapper;
     stream = wrapper;
 
 
     // Validate the password by confirming that the encryption header matches.
     // Validate the password by confirming that the encryption header matches.
-    char this_header[_encrypt_header_size];
-    stream->read(this_header, _encrypt_header_size);
-    if (stream->fail() || stream->gcount() != (unsigned)_encrypt_header_size ||
-        memcmp(this_header, _encrypt_header, _encrypt_header_size) != 0) {
+    if (!wrapper->read_magic(_encrypt_header, _encrypt_header_size)) {
       express_cat.error()
       express_cat.error()
         << "Unable to decrypt subfile " << subfile->_name << ".\n";
         << "Unable to decrypt subfile " << subfile->_name << ".\n";
       delete stream;
       delete stream;

+ 2 - 2
panda/src/express/zStreamBuf.cxx

@@ -202,8 +202,8 @@ seekoff(streamoff off, ios_seekdir dir, ios_openmode which) {
 
 
   gbump(n);
   gbump(n);
 
 
-  _source->seekg(0, ios::beg);
-  if (_source->tellg() == (streampos)0) {
+  if (_source->rdbuf()->pubseekpos(0, ios::in) == (streampos)0) {
+    _source->clear();
     _z_source.next_in = Z_NULL;
     _z_source.next_in = Z_NULL;
     _z_source.avail_in = 0;
     _z_source.avail_in = 0;
     _z_source.next_out = Z_NULL;
     _z_source.next_out = Z_NULL;

+ 2 - 1
panda/src/ffmpeg/ffmpegAudioCursor.cxx

@@ -462,7 +462,7 @@ seek(double t) {
  * read.  Your buffer must be equal in size to N * channels.  Multiple-channel
  * read.  Your buffer must be equal in size to N * channels.  Multiple-channel
  * audio will be interleaved.
  * audio will be interleaved.
  */
  */
-void FfmpegAudioCursor::
+int FfmpegAudioCursor::
 read_samples(int n, int16_t *data) {
 read_samples(int n, int16_t *data) {
   int desired = n * _audio_channels;
   int desired = n * _audio_channels;
 
 
@@ -486,4 +486,5 @@ read_samples(int n, int16_t *data) {
 
 
   }
   }
   _samples_read += n;
   _samples_read += n;
+  return n;
 }
 }

+ 1 - 1
panda/src/ffmpeg/ffmpegAudioCursor.h

@@ -45,7 +45,7 @@ PUBLISHED:
   virtual void seek(double offset);
   virtual void seek(double offset);
 
 
 public:
 public:
-  virtual void read_samples(int n, int16_t *data);
+  virtual int read_samples(int n, int16_t *data);
 
 
 protected:
 protected:
   void fetch_packet();
   void fetch_packet();

+ 3 - 1
panda/src/ffmpeg/ffmpegVideoCursor.cxx

@@ -755,7 +755,9 @@ do_poll() {
       PT(FfmpegBuffer) frame = do_alloc_frame();
       PT(FfmpegBuffer) frame = do_alloc_frame();
       nassertr(frame != nullptr, false);
       nassertr(frame != nullptr, false);
       _lock.release();
       _lock.release();
-      advance_to_frame(seek_frame);
+      if (seek_frame != _begin_frame) {
+        advance_to_frame(seek_frame);
+      }
       if (_frame_ready) {
       if (_frame_ready) {
         export_frame(frame);
         export_frame(frame);
         _lock.acquire();
         _lock.acquire();

+ 25 - 2
panda/src/glstuff/glGraphicsStateGuardian_src.cxx

@@ -597,8 +597,31 @@ reset() {
   query_glsl_version();
   query_glsl_version();
 
 
 #ifndef OPENGLES
 #ifndef OPENGLES
-  bool core_profile = is_at_least_gl_version(3, 2) &&
-                      !has_extension("GL_ARB_compatibility");
+  // Determine whether this OpenGL context has compatibility features.
+  bool core_profile = false;
+
+  if (_gl_version_major >= 3) {
+    if (_gl_version_major > 3 || _gl_version_minor >= 2) {
+      // OpenGL 3.2 has a built-in way to check this.
+      GLint profile_mask = 0;
+      glGetIntegerv(GL_CONTEXT_PROFILE_MASK, &profile_mask);
+
+      if (profile_mask & GL_CONTEXT_CORE_PROFILE_BIT) {
+        core_profile = true;
+      } else if (profile_mask & GL_CONTEXT_COMPATIBILITY_PROFILE_BIT) {
+        core_profile = false;
+      } else {
+        core_profile = !has_extension("GL_ARB_compatibility");
+      }
+    } else {
+      // OpenGL 3.0/3.1.
+      GLint flags = 0;
+      glGetIntegerv(GL_CONTEXT_FLAGS, &flags);
+      if (flags & GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT) {
+        core_profile = true;
+      }
+    }
+  }
 
 
   if (GLCAT.is_debug()) {
   if (GLCAT.is_debug()) {
     if (core_profile) {
     if (core_profile) {

+ 24 - 20
panda/src/glstuff/glShaderContext_src.cxx

@@ -2110,8 +2110,8 @@ issue_parameters(int altered) {
     for (int i = 0; i < (int)_shader->_ptr_spec.size(); ++i) {
     for (int i = 0; i < (int)_shader->_ptr_spec.size(); ++i) {
       Shader::ShaderPtrSpec &spec = _shader->_ptr_spec[i];
       Shader::ShaderPtrSpec &spec = _shader->_ptr_spec[i];
 
 
-      const Shader::ShaderPtrData* ptr_data = _glgsg->fetch_ptr_parameter(spec);
-      if (ptr_data == nullptr) { //the input is not contained in ShaderPtrData
+      Shader::ShaderPtrData ptr_data;
+      if (!_glgsg->fetch_ptr_parameter(spec, ptr_data)) { //the input is not contained in ShaderPtrData
         release_resources();
         release_resources();
         return;
         return;
       }
       }
@@ -2119,18 +2119,18 @@ issue_parameters(int altered) {
       nassertd(spec._dim[1] > 0) continue;
       nassertd(spec._dim[1] > 0) continue;
 
 
       GLint p = spec._id._seqno;
       GLint p = spec._id._seqno;
-      int array_size = min(spec._dim[0], (int)ptr_data->_size / spec._dim[1]);
+      int array_size = min(spec._dim[0], (int)ptr_data._size / spec._dim[1]);
       switch (spec._type) {
       switch (spec._type) {
       case Shader::SPT_float:
       case Shader::SPT_float:
         {
         {
           float *data = nullptr;
           float *data = nullptr;
 
 
-          switch (ptr_data->_type) {
+          switch (ptr_data._type) {
           case Shader::SPT_int:
           case Shader::SPT_int:
             // Convert int data to float data.
             // Convert int data to float data.
             data = (float*) alloca(sizeof(float) * array_size * spec._dim[1]);
             data = (float*) alloca(sizeof(float) * array_size * spec._dim[1]);
             for (int i = 0; i < (array_size * spec._dim[1]); ++i) {
             for (int i = 0; i < (array_size * spec._dim[1]); ++i) {
-              data[i] = (float)(((int*)ptr_data->_ptr)[i]);
+              data[i] = (float)(((int*)ptr_data._ptr)[i]);
             }
             }
             break;
             break;
 
 
@@ -2138,7 +2138,7 @@ issue_parameters(int altered) {
             // Convert unsigned int data to float data.
             // Convert unsigned int data to float data.
             data = (float*) alloca(sizeof(float) * array_size * spec._dim[1]);
             data = (float*) alloca(sizeof(float) * array_size * spec._dim[1]);
             for (int i = 0; i < (array_size * spec._dim[1]); ++i) {
             for (int i = 0; i < (array_size * spec._dim[1]); ++i) {
-              data[i] = (float)(((unsigned int*)ptr_data->_ptr)[i]);
+              data[i] = (float)(((unsigned int*)ptr_data._ptr)[i]);
             }
             }
             break;
             break;
 
 
@@ -2146,12 +2146,12 @@ issue_parameters(int altered) {
             // Downgrade double data to float data.
             // Downgrade double data to float data.
             data = (float*) alloca(sizeof(float) * array_size * spec._dim[1]);
             data = (float*) alloca(sizeof(float) * array_size * spec._dim[1]);
             for (int i = 0; i < (array_size * spec._dim[1]); ++i) {
             for (int i = 0; i < (array_size * spec._dim[1]); ++i) {
-              data[i] = (float)(((double*)ptr_data->_ptr)[i]);
+              data[i] = (float)(((double*)ptr_data._ptr)[i]);
             }
             }
             break;
             break;
 
 
           case Shader::SPT_float:
           case Shader::SPT_float:
-            data = (float*)ptr_data->_ptr;
+            data = (float*)ptr_data._ptr;
             break;
             break;
 
 
           default:
           default:
@@ -2171,8 +2171,8 @@ issue_parameters(int altered) {
         break;
         break;
 
 
       case Shader::SPT_int:
       case Shader::SPT_int:
-        if (ptr_data->_type != Shader::SPT_int &&
-            ptr_data->_type != Shader::SPT_uint) {
+        if (ptr_data._type != Shader::SPT_int &&
+            ptr_data._type != Shader::SPT_uint) {
           GLCAT.error()
           GLCAT.error()
             << "Cannot pass floating-point data to integer shader input '" << spec._id._name << "'\n";
             << "Cannot pass floating-point data to integer shader input '" << spec._id._name << "'\n";
 
 
@@ -2183,18 +2183,18 @@ issue_parameters(int altered) {
 
 
         } else {
         } else {
           switch (spec._dim[1]) {
           switch (spec._dim[1]) {
-          case 1: _glgsg->_glUniform1iv(p, array_size, (int*)ptr_data->_ptr); continue;
-          case 2: _glgsg->_glUniform2iv(p, array_size, (int*)ptr_data->_ptr); continue;
-          case 3: _glgsg->_glUniform3iv(p, array_size, (int*)ptr_data->_ptr); continue;
-          case 4: _glgsg->_glUniform4iv(p, array_size, (int*)ptr_data->_ptr); continue;
+          case 1: _glgsg->_glUniform1iv(p, array_size, (int*)ptr_data._ptr); continue;
+          case 2: _glgsg->_glUniform2iv(p, array_size, (int*)ptr_data._ptr); continue;
+          case 3: _glgsg->_glUniform3iv(p, array_size, (int*)ptr_data._ptr); continue;
+          case 4: _glgsg->_glUniform4iv(p, array_size, (int*)ptr_data._ptr); continue;
           }
           }
           nassertd(false) continue;
           nassertd(false) continue;
         }
         }
         break;
         break;
 
 
       case Shader::SPT_uint:
       case Shader::SPT_uint:
-        if (ptr_data->_type != Shader::SPT_uint &&
-            ptr_data->_type != Shader::SPT_int) {
+        if (ptr_data._type != Shader::SPT_uint &&
+            ptr_data._type != Shader::SPT_int) {
           GLCAT.error()
           GLCAT.error()
             << "Cannot pass floating-point data to integer shader input '" << spec._id._name << "'\n";
             << "Cannot pass floating-point data to integer shader input '" << spec._id._name << "'\n";
 
 
@@ -2205,10 +2205,10 @@ issue_parameters(int altered) {
 
 
         } else {
         } else {
           switch (spec._dim[1]) {
           switch (spec._dim[1]) {
-          case 1: _glgsg->_glUniform1uiv(p, array_size, (GLuint *)ptr_data->_ptr); continue;
-          case 2: _glgsg->_glUniform2uiv(p, array_size, (GLuint *)ptr_data->_ptr); continue;
-          case 3: _glgsg->_glUniform3uiv(p, array_size, (GLuint *)ptr_data->_ptr); continue;
-          case 4: _glgsg->_glUniform4uiv(p, array_size, (GLuint *)ptr_data->_ptr); continue;
+          case 1: _glgsg->_glUniform1uiv(p, array_size, (GLuint *)ptr_data._ptr); continue;
+          case 2: _glgsg->_glUniform2uiv(p, array_size, (GLuint *)ptr_data._ptr); continue;
+          case 3: _glgsg->_glUniform3uiv(p, array_size, (GLuint *)ptr_data._ptr); continue;
+          case 4: _glgsg->_glUniform4uiv(p, array_size, (GLuint *)ptr_data._ptr); continue;
           }
           }
           nassertd(false) continue;
           nassertd(false) continue;
         }
         }
@@ -3206,6 +3206,10 @@ glsl_compile_and_link() {
     valid &= glsl_compile_shader(Shader::ST_compute);
     valid &= glsl_compile_shader(Shader::ST_compute);
   }
   }
 
 
+  if (!valid) {
+    return false;
+  }
+
   // There might be warnings, so report those.  GLSLShaders::const_iterator
   // There might be warnings, so report those.  GLSLShaders::const_iterator
   // it; for (it = _glsl_shaders.begin(); it != _glsl_shaders.end(); ++it) {
   // it; for (it = _glsl_shaders.begin(); it != _glsl_shaders.end(); ++it) {
   // glsl_report_shader_errors(*it); }
   // glsl_report_shader_errors(*it); }

+ 5 - 0
panda/src/glstuff/glmisc_src.cxx

@@ -17,6 +17,11 @@ ConfigVariableInt gl_version
   ("gl-version", "",
   ("gl-version", "",
    PRC_DESC("Set this to get an OpenGL context with a specific version."));
    PRC_DESC("Set this to get an OpenGL context with a specific version."));
 
 
+ConfigVariableBool gl_forward_compatible
+  ("gl-forward-compatible", false,
+   PRC_DESC("Setting this to true will request a forward-compatible OpenGL "
+            "context, which will not support the fixed-function pipeline."));
+
 ConfigVariableBool gl_support_fbo
 ConfigVariableBool gl_support_fbo
   ("gl-support-fbo", true,
   ("gl-support-fbo", true,
    PRC_DESC("Configure this false if your GL's implementation of "
    PRC_DESC("Configure this false if your GL's implementation of "

+ 1 - 0
panda/src/glstuff/glmisc_src.h

@@ -41,6 +41,7 @@
 // #define GSG_VERBOSE 1
 // #define GSG_VERBOSE 1
 
 
 extern EXPCL_GL ConfigVariableInt gl_version;
 extern EXPCL_GL ConfigVariableInt gl_version;
+extern EXPCL_GL ConfigVariableBool gl_forward_compatible;
 extern EXPCL_GL ConfigVariableBool gl_support_fbo;
 extern EXPCL_GL ConfigVariableBool gl_support_fbo;
 extern ConfigVariableBool gl_cheap_textures;
 extern ConfigVariableBool gl_cheap_textures;
 extern ConfigVariableBool gl_ignore_clamp;
 extern ConfigVariableBool gl_ignore_clamp;

+ 12 - 1
panda/src/glxdisplay/glxGraphicsStateGuardian.cxx

@@ -352,9 +352,20 @@ choose_pixel_format(const FrameBufferProperties &properties,
           attrib_list[n++] = gl_version[1];
           attrib_list[n++] = gl_version[1];
         }
         }
       }
       }
+      int flags = 0;
       if (gl_debug) {
       if (gl_debug) {
+        flags |= GLX_CONTEXT_DEBUG_BIT_ARB;
+      }
+      if (gl_forward_compatible) {
+        flags |= GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB;
+        if (gl_version.get_num_words() == 0 || gl_version[0] < 2) {
+          glxdisplay_cat.error()
+            << "gl-forward-compatible requires gl-version >= 3 0\n";
+        }
+      }
+      if (flags != 0) {
         attrib_list[n++] = GLX_CONTEXT_FLAGS_ARB;
         attrib_list[n++] = GLX_CONTEXT_FLAGS_ARB;
-        attrib_list[n++] = GLX_CONTEXT_DEBUG_BIT_ARB;
+        attrib_list[n++] = flags;
       }
       }
       attrib_list[n] = None;
       attrib_list[n] = None;
       _context = _glXCreateContextAttribs(_display, _fbconfig, _share_context,
       _context = _glXCreateContextAttribs(_display, _fbconfig, _share_context,

+ 0 - 8
panda/src/linmath/lmatrix4_src.I

@@ -484,13 +484,9 @@ get_col(int col) const {
  */
  */
 INLINE_LINMATH FLOATNAME(LVecBase3) FLOATNAME(LMatrix4)::
 INLINE_LINMATH FLOATNAME(LVecBase3) FLOATNAME(LMatrix4)::
 get_row3(int row) const {
 get_row3(int row) const {
-#ifdef HAVE_EIGEN
-  return FLOATNAME(LVecBase3)(_m.block<1, 3>(row, 0));
-#else
   return FLOATNAME(LVecBase3)((*this)(row, 0),
   return FLOATNAME(LVecBase3)((*this)(row, 0),
                               (*this)(row, 1),
                               (*this)(row, 1),
                               (*this)(row, 2));
                               (*this)(row, 2));
-#endif  // HAVE_EIGEN
 }
 }
 
 
 /**
 /**
@@ -514,13 +510,9 @@ get_row3(FLOATNAME(LVecBase3) &result_vec,int row) const {
  */
  */
 INLINE_LINMATH FLOATNAME(LVecBase3) FLOATNAME(LMatrix4)::
 INLINE_LINMATH FLOATNAME(LVecBase3) FLOATNAME(LMatrix4)::
 get_col3(int col) const {
 get_col3(int col) const {
-#ifdef HAVE_EIGEN
-  return FLOATNAME(LVecBase3)(_m.block<1, 3>(0, col));
-#else
   return FLOATNAME(LVecBase3)((*this)(0, col),
   return FLOATNAME(LVecBase3)((*this)(0, col),
                               (*this)(1, col),
                               (*this)(1, col),
                               (*this)(2, col));
                               (*this)(2, col));
-#endif  // HAVE_EIGEN
 }
 }
 
 
 /**
 /**

+ 7 - 0
panda/src/mathutil/perlinNoise2.cxx

@@ -19,6 +19,9 @@
  */
  */
 double PerlinNoise2::
 double PerlinNoise2::
 noise(const LVecBase2d &value) const {
 noise(const LVecBase2d &value) const {
+  // If this triggers, you passed in 0 for table_size.
+  nassertr(!_index.empty(), make_nan(0.0));
+
   // Convert the vector to our local coordinate space.
   // Convert the vector to our local coordinate space.
   LVecBase2d vec = _input_xform.xform_point(value);
   LVecBase2d vec = _input_xform.xform_point(value);
 
 
@@ -41,9 +44,13 @@ noise(const LVecBase2d &value) const {
   double v = fade(y);
   double v = fade(y);
 
 
   // Hash coordinates of the 4 square corners (A, B, A + 1, and B + 1)
   // Hash coordinates of the 4 square corners (A, B, A + 1, and B + 1)
+  nassertr(X >= 0 && X + 1 < _index.size(), make_nan(0.0));
   int A = _index[X] + Y;
   int A = _index[X] + Y;
   int B = _index[X + 1] + Y;
   int B = _index[X + 1] + Y;
 
 
+  nassertr(A >= 0 && A + 1 < _index.size(), make_nan(0.0));
+  nassertr(B >= 0 && B + 1 < _index.size(), make_nan(0.0));
+
   // and add blended results from 4 corners of square.
   // and add blended results from 4 corners of square.
   double result =
   double result =
     lerp(v, lerp(u, grad(_index[A], x, y),
     lerp(v, lerp(u, grad(_index[A], x, y),

+ 4 - 2
panda/src/movies/flacAudioCursor.cxx

@@ -118,8 +118,10 @@ seek(double t) {
  * read.  Your buffer must be equal in size to N * channels.  Multiple-channel
  * read.  Your buffer must be equal in size to N * channels.  Multiple-channel
  * audio will be interleaved.
  * audio will be interleaved.
  */
  */
-void FlacAudioCursor::
+int FlacAudioCursor::
 read_samples(int n, int16_t *data) {
 read_samples(int n, int16_t *data) {
   int desired = n * _audio_channels;
   int desired = n * _audio_channels;
-  _samples_read += drflac_read_s16(_drflac, desired, data) / _audio_channels;
+  n = drflac_read_s16(_drflac, desired, data) / _audio_channels;
+  _samples_read += n;
+  return n;
 }
 }

+ 1 - 1
panda/src/movies/flacAudioCursor.h

@@ -37,7 +37,7 @@ PUBLISHED:
   virtual void seek(double offset);
   virtual void seek(double offset);
 
 
 public:
 public:
-  virtual void read_samples(int n, int16_t *data);
+  virtual int read_samples(int n, int16_t *data);
 
 
   bool _is_valid;
   bool _is_valid;
 
 

+ 3 - 2
panda/src/movies/microphoneAudioDS.cxx

@@ -91,7 +91,7 @@ public:
   int _samples_per_buffer;
   int _samples_per_buffer;
 
 
 public:
 public:
-  virtual void read_samples(int n, int16_t *data);
+  virtual int read_samples(int n, int16_t *data);
   virtual int ready() const;
   virtual int ready() const;
 
 
 public:
 public:
@@ -323,7 +323,7 @@ MicrophoneAudioCursorDS::
 /**
 /**
  *
  *
  */
  */
-void MicrophoneAudioCursorDS::
+int MicrophoneAudioCursorDS::
 read_samples(int n, int16_t *data) {
 read_samples(int n, int16_t *data) {
   int orign = n;
   int orign = n;
   if (_handle) {
   if (_handle) {
@@ -373,6 +373,7 @@ read_samples(int n, int16_t *data) {
   if (n > 0) {
   if (n > 0) {
     memcpy(data, 0, n*2*_audio_channels);
     memcpy(data, 0, n*2*_audio_channels);
   }
   }
+  return orign - n;
 }
 }
 
 
 /**
 /**

+ 18 - 12
panda/src/movies/movieAudioCursor.cxx

@@ -45,14 +45,14 @@ MovieAudioCursor::
  * read.  Your buffer must be equal in size to N * channels.  Multiple-channel
  * read.  Your buffer must be equal in size to N * channels.  Multiple-channel
  * audio will be interleaved.
  * audio will be interleaved.
  */
  */
-void MovieAudioCursor::
+int MovieAudioCursor::
 read_samples(int n, int16_t *data) {
 read_samples(int n, int16_t *data) {
 
 
   // This is the null implementation, which generates pure silence.  Normally,
   // This is the null implementation, which generates pure silence.  Normally,
   // this method will be overridden by a subclass.
   // this method will be overridden by a subclass.
 
 
   if (n <= 0) {
   if (n <= 0) {
-    return;
+    return 0;
   }
   }
 
 
   int desired = n * _audio_channels;
   int desired = n * _audio_channels;
@@ -60,6 +60,7 @@ read_samples(int n, int16_t *data) {
     data[i] = 0;
     data[i] = 0;
   }
   }
   _samples_read += n;
   _samples_read += n;
+  return n;
 }
 }
 
 
 /**
 /**
@@ -92,23 +93,28 @@ read_samples(int n, Datagram *dg) {
  * This is not particularly efficient, but it may be a convenient way to
  * This is not particularly efficient, but it may be a convenient way to
  * manipulate samples in python.
  * manipulate samples in python.
  */
  */
-std::string MovieAudioCursor::
+vector_uchar MovieAudioCursor::
 read_samples(int n) {
 read_samples(int n) {
-  std::ostringstream result;
+  vector_uchar result;
   int16_t tmp[4096];
   int16_t tmp[4096];
   while (n > 0) {
   while (n > 0) {
     int blocksize = (4096 / _audio_channels);
     int blocksize = (4096 / _audio_channels);
-    if (blocksize > n) blocksize = n;
-    int words = blocksize * _audio_channels;
-    read_samples(blocksize, tmp);
-    for (int i=0; i<words; i++) {
+    if (blocksize > n) {
+      blocksize = n;
+    }
+    int nread = read_samples(blocksize, tmp);
+    if (nread == 0) {
+      return result;
+    }
+    int words = nread * _audio_channels;
+    for (int i = 0; i < words; ++i) {
       int16_t word = tmp[i];
       int16_t word = tmp[i];
-      result.put((char)(word & 255));
-      result.put((char)((word>>8) & 255));
+      result.push_back((uint8_t)(word & 255u));
+      result.push_back((uint8_t)((word >> 8) & 255u));
     }
     }
-    n -= blocksize;
+    n -= nread;
   }
   }
-  return result.str();
+  return result;
 }
 }
 
 
 
 

+ 2 - 2
panda/src/movies/movieAudioCursor.h

@@ -48,10 +48,10 @@ PUBLISHED:
   virtual int ready() const;
   virtual int ready() const;
   virtual void seek(double offset);
   virtual void seek(double offset);
   void read_samples(int n, Datagram *dg);
   void read_samples(int n, Datagram *dg);
-  std::string read_samples(int n);
+  vector_uchar read_samples(int n);
 
 
 public:
 public:
-  virtual void read_samples(int n, int16_t *data);
+  virtual int read_samples(int n, int16_t *data);
 
 
 protected:
 protected:
   PT(MovieAudio) _source;
   PT(MovieAudio) _source;

+ 12 - 0
panda/src/movies/movieTypeRegistry.cxx

@@ -31,6 +31,12 @@ PT(MovieAudio) MovieTypeRegistry::
 make_audio(const Filename &name) {
 make_audio(const Filename &name) {
   string ext = downcase(name.get_extension());
   string ext = downcase(name.get_extension());
 
 
+#ifdef HAVE_ZLIB
+  if (ext == "pz" || ext == "gz") {
+    ext = Filename(name.get_basename_wo_extension()).get_extension();
+  }
+#endif
+
   _audio_lock.lock();
   _audio_lock.lock();
 
 
   // Make sure that the list of audio types has been read in.
   // Make sure that the list of audio types has been read in.
@@ -154,6 +160,12 @@ PT(MovieVideo) MovieTypeRegistry::
 make_video(const Filename &name) {
 make_video(const Filename &name) {
   string ext = downcase(name.get_extension());
   string ext = downcase(name.get_extension());
 
 
+#ifdef HAVE_ZLIB
+  if (ext == "pz" || ext == "gz") {
+    ext = Filename(name.get_basename_wo_extension()).get_extension();
+  }
+#endif
+
   _video_lock.lock();
   _video_lock.lock();
 
 
   // Make sure that the list of video types has been read in.
   // Make sure that the list of video types has been read in.

+ 55 - 12
panda/src/movies/opusAudioCursor.cxx

@@ -56,6 +56,22 @@ int cb_seek(void *stream, opus_int64 offset, int whence) {
     break;
     break;
 
 
   case SEEK_CUR:
   case SEEK_CUR:
+    // opusfile uses a seek with offset 0 to determine whether seeking is
+    // supported, but this is not good enough.  We seek to the end and back.
+    if (offset == 0) {
+      std::streambuf *buf = in->rdbuf();
+      std::streampos pos = buf->pubseekoff(0, std::ios::cur, std::ios::in);
+      if (pos < 0) {
+        return -1;
+      }
+      if (buf->pubseekoff(0, std::ios::end, std::ios::in) >= 0) {
+        // It worked; seek back to the previous location.
+        buf->pubseekpos(pos, std::ios::in);
+        return 0;
+      } else {
+        return -1;
+      }
+    }
     in->seekg(offset, std::ios::cur);
     in->seekg(offset, std::ios::cur);
     break;
     break;
 
 
@@ -101,16 +117,7 @@ opus_int64 cb_tell(void *stream) {
   return in->tellg();
   return in->tellg();
 }
 }
 
 
-int cb_close(void *stream) {
-  istream *in = (istream *)stream;
-  nassertr(in != nullptr, EOF);
-
-  VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
-  vfs->close_read_file(in);
-  return 0;
-}
-
-static const OpusFileCallbacks callbacks = {cb_read, cb_seek, cb_tell, cb_close};
+static const OpusFileCallbacks callbacks = {cb_read, cb_seek, cb_tell, nullptr};
 
 
 TypeHandle OpusAudioCursor::_type_handle;
 TypeHandle OpusAudioCursor::_type_handle;
 
 
@@ -122,6 +129,7 @@ OpusAudioCursor::
 OpusAudioCursor(OpusAudio *src, istream *stream) :
 OpusAudioCursor(OpusAudio *src, istream *stream) :
   MovieAudioCursor(src),
   MovieAudioCursor(src),
   _is_valid(false),
   _is_valid(false),
+  _stream(stream),
   _link(0)
   _link(0)
 {
 {
   nassertv(stream != nullptr);
   nassertv(stream != nullptr);
@@ -159,6 +167,11 @@ OpusAudioCursor::
     op_free(_op);
     op_free(_op);
     _op = nullptr;
     _op = nullptr;
   }
   }
+
+  if (_stream != nullptr) {
+    VirtualFileSystem::close_read_file(_stream);
+    _stream = nullptr;
+  }
 }
 }
 
 
 /**
 /**
@@ -174,7 +187,32 @@ seek(double t) {
   t = std::max(t, 0.0);
   t = std::max(t, 0.0);
 
 
   // Use op_time_seek_lap if cross-lapping is enabled.
   // Use op_time_seek_lap if cross-lapping is enabled.
-  int error = op_pcm_seek(_op, (ogg_int64_t)(t * 48000.0));
+  ogg_int64_t sample = (ogg_int64_t)(t * 48000.0);
+  int error = op_pcm_seek(_op, sample);
+
+  // Special case for seeking to the beginning; if normal seek fails, we may
+  // be able to explicitly seek to the beginning of the file and call op_open
+  // again.  This allows looping compressed .opus files.
+  if (error == OP_ENOSEEK && sample == 0) {
+    if (_stream->rdbuf()->pubseekpos(0, std::ios::in) == (std::streampos)0) {
+      OggOpusFile *op = op_open_callbacks((void *)_stream, &callbacks, nullptr, 0, nullptr);
+      if (op != nullptr) {
+        op_free(_op);
+        _op = op;
+      } else {
+        movies_cat.error()
+          << "Failed to reopen Opus file to seek to beginning.\n";
+        return;
+      }
+
+      // Reset this field for good measure, just in case this changed.
+      _audio_channels = op_channel_count(_op, -1);
+
+      _last_seek = 0.0;
+      _samples_read = 0;
+      return;
+    }
+  }
   if (error != 0) {
   if (error != 0) {
     movies_cat.error()
     movies_cat.error()
       << "Seek failed (error " << error << ").  Opus stream may not be seekable.\n";
       << "Seek failed (error " << error << ").  Opus stream may not be seekable.\n";
@@ -190,7 +228,7 @@ seek(double t) {
  * read.  Your buffer must be equal in size to N * channels.  Multiple-channel
  * read.  Your buffer must be equal in size to N * channels.  Multiple-channel
  * audio will be interleaved.
  * audio will be interleaved.
  */
  */
-void OpusAudioCursor::
+int OpusAudioCursor::
 read_samples(int n, int16_t *data) {
 read_samples(int n, int16_t *data) {
   int16_t *end = data + (n * _audio_channels);
   int16_t *end = data + (n * _audio_channels);
 
 
@@ -202,6 +240,9 @@ read_samples(int n, int16_t *data) {
       data += read_samples * _audio_channels;
       data += read_samples * _audio_channels;
       _samples_read += read_samples;
       _samples_read += read_samples;
     } else {
     } else {
+      if (read_samples == 0 && _length == 1.0E10) {
+        _length = op_pcm_tell(_op) / 48000.0;
+      }
       break;
       break;
     }
     }
 
 
@@ -224,7 +265,9 @@ read_samples(int n, int16_t *data) {
   // Fill the rest of the buffer with silence.
   // Fill the rest of the buffer with silence.
   if (data < end) {
   if (data < end) {
     memset(data, 0, (unsigned char *)end - (unsigned char *)data);
     memset(data, 0, (unsigned char *)end - (unsigned char *)data);
+    n -= (end - data) / _audio_channels;
   }
   }
+  return n;
 }
 }
 
 
 #endif // HAVE_OPUS
 #endif // HAVE_OPUS

+ 2 - 10
panda/src/movies/opusAudioCursor.h

@@ -39,22 +39,14 @@ PUBLISHED:
   virtual void seek(double offset);
   virtual void seek(double offset);
 
 
 public:
 public:
-  virtual void read_samples(int n, int16_t *data);
+  virtual int read_samples(int n, int16_t *data);
 
 
   bool _is_valid;
   bool _is_valid;
 
 
 protected:
 protected:
   OggOpusFile *_op;
   OggOpusFile *_op;
-
+  std::istream *_stream;
   int _link;
   int _link;
-  double _byte_rate;
-  int _block_align;
-  int _bytes_per_sample;
-  bool _is_float;
-
-  std::streampos _data_start;
-  std::streampos _data_pos;
-  size_t _data_size;
 
 
 public:
 public:
   static TypeHandle get_class_type() {
   static TypeHandle get_class_type() {

+ 8 - 5
panda/src/movies/userDataAudio.cxx

@@ -57,12 +57,14 @@ open() {
  * read.  Your buffer must be equal in size to N * channels.  Multiple-channel
  * read.  Your buffer must be equal in size to N * channels.  Multiple-channel
  * audio will be interleaved.
  * audio will be interleaved.
  */
  */
-void UserDataAudio::
+int UserDataAudio::
 read_samples(int n, int16_t *data) {
 read_samples(int n, int16_t *data) {
   int ready = (_data.size() / _desired_channels);
   int ready = (_data.size() / _desired_channels);
   int desired = n * _desired_channels;
   int desired = n * _desired_channels;
-  int avail = ready * _desired_channels;
-  if (avail > desired) avail = desired;
+  if (n > ready) {
+    n = ready;
+  }
+  int avail = n * _desired_channels;
   for (int i=0; i<avail; i++) {
   for (int i=0; i<avail; i++) {
     data[i] = _data[i];
     data[i] = _data[i];
   }
   }
@@ -72,6 +74,7 @@ read_samples(int n, int16_t *data) {
   for (int i=0; i<avail; i++) {
   for (int i=0; i<avail; i++) {
     _data.pop_front();
     _data.pop_front();
   }
   }
+  return n;
 }
 }
 
 
 /**
 /**
@@ -107,11 +110,11 @@ append(DatagramIterator *src, int n) {
  * but it may be convenient to deal with samples in python.
  * but it may be convenient to deal with samples in python.
  */
  */
 void UserDataAudio::
 void UserDataAudio::
-append(const std::string &str) {
+append(const vector_uchar &str) {
   nassertv(!_aborted);
   nassertv(!_aborted);
   int samples = str.size() / (2 * _desired_channels);
   int samples = str.size() / (2 * _desired_channels);
   int words = samples * _desired_channels;
   int words = samples * _desired_channels;
-  for (int i=0; i<words; i++) {
+  for (int i = 0; i < words; ++i) {
     int c1 = ((unsigned char)str[i*2+0]);
     int c1 = ((unsigned char)str[i*2+0]);
     int c2 = ((unsigned char)str[i*2+1]);
     int c2 = ((unsigned char)str[i*2+1]);
     int16_t n = (c1 | (c2 << 8));
     int16_t n = (c1 | (c2 << 8));

+ 2 - 2
panda/src/movies/userDataAudio.h

@@ -37,11 +37,11 @@ class EXPCL_PANDA_MOVIES UserDataAudio : public MovieAudio {
 
 
   void append(int16_t *data, int n);
   void append(int16_t *data, int n);
   void append(DatagramIterator *src, int len=0x40000000);
   void append(DatagramIterator *src, int len=0x40000000);
-  void append(const std::string &str);
+  void append(const vector_uchar &);
   void done(); // A promise not to write any more samples.
   void done(); // A promise not to write any more samples.
 
 
  private:
  private:
-  void read_samples(int n, int16_t *data);
+  int read_samples(int n, int16_t *data);
   void update_cursor();
   void update_cursor();
   int _desired_rate;
   int _desired_rate;
   int _desired_channels;
   int _desired_channels;

+ 6 - 3
panda/src/movies/userDataAudioCursor.cxx

@@ -47,12 +47,12 @@ UserDataAudioCursor::
  * read.  Your buffer must be equal in size to N * channels.  Multiple-channel
  * read.  Your buffer must be equal in size to N * channels.  Multiple-channel
  * audio will be interleaved.
  * audio will be interleaved.
  */
  */
-void UserDataAudioCursor::
+int UserDataAudioCursor::
 read_samples(int n, int16_t *data) {
 read_samples(int n, int16_t *data) {
   UserDataAudio *source = (UserDataAudio*)(MovieAudio*)_source;
   UserDataAudio *source = (UserDataAudio*)(MovieAudio*)_source;
 
 
-  if(source->_remove_after_read) {
-    source->read_samples(n, data);
+  if (source->_remove_after_read) {
+    n = source->read_samples(n, data);
   }
   }
   else {
   else {
     int offset = _samples_read * _audio_channels;
     int offset = _samples_read * _audio_channels;
@@ -66,9 +66,12 @@ read_samples(int n, int16_t *data) {
     for (int i=avail; i<desired; i++) {
     for (int i=avail; i<desired; i++) {
       data[i] = 0;
       data[i] = 0;
     }
     }
+
+    n = avail / _audio_channels;
   }
   }
 
 
   _samples_read += n;
   _samples_read += n;
+  return n;
 }
 }
 
 
 /**
 /**

+ 1 - 1
panda/src/movies/userDataAudioCursor.h

@@ -33,7 +33,7 @@ PUBLISHED:
   virtual ~UserDataAudioCursor();
   virtual ~UserDataAudioCursor();
 
 
 public:
 public:
-  virtual void read_samples(int n, int16_t *data);
+  virtual int read_samples(int n, int16_t *data);
   virtual int ready() const;
   virtual int ready() const;
   virtual void seek(double offset);
   virtual void seek(double offset);
 
 

+ 56 - 9
panda/src/movies/vorbisAudioCursor.cxx

@@ -91,19 +91,46 @@ seek(double t) {
   t = std::max(t, 0.0);
   t = std::max(t, 0.0);
 
 
   // Use ov_time_seek_lap if cross-lapping is enabled.
   // Use ov_time_seek_lap if cross-lapping is enabled.
+  int result;
   if (vorbis_seek_lap) {
   if (vorbis_seek_lap) {
-    if (ov_time_seek_lap(&_ov, t) != 0) {
-      movies_cat.error()
-        << "Seek failed.  Ogg Vorbis stream may not be seekable.\n";
-      return;
-    }
+    result = ov_time_seek_lap(&_ov, t);
   } else {
   } else {
-    if (ov_time_seek(&_ov, t) != 0) {
-      movies_cat.error()
-        << "Seek failed.  Ogg Vorbis stream may not be seekable.\n";
+    result = ov_time_seek(&_ov, t);
+  }
+
+  // Special case for seeking to the beginning; if normal seek fails, we may
+  // be able to explicitly seek to the beginning of the file and call ov_open
+  // again.  This allows looping compressed .ogg files.
+  if (result == OV_ENOSEEK && t == 0.0) {
+    std::istream *stream = (std::istream *)_ov.datasource;
+
+    if (stream->rdbuf()->pubseekpos(0, std::ios::in) == (std::streampos)0) {
+      // Back up the callbacks, then destroy the stream, making sure to first
+      // unset the datasource so that it won't close the file.
+      ov_callbacks callbacks = _ov.callbacks;
+      _ov.datasource = nullptr;
+      ov_clear(&_ov);
+
+      if (ov_open_callbacks((void *)stream, &_ov, nullptr, 0, callbacks) != 0) {
+        movies_cat.error()
+          << "Failed to reopen Ogg Vorbis file to seek to beginning.\n";
+        return;
+      }
+
+      // Reset these fields for good measure, just in case the file changed.
+      vorbis_info *vi = ov_info(&_ov, -1);
+      _audio_channels = vi->channels;
+      _audio_rate = vi->rate;
+
+      _last_seek = 0.0;
+      _samples_read = 0;
       return;
       return;
     }
     }
   }
   }
+  if (result != 0) {
+    movies_cat.error()
+      << "Seek failed.  Ogg Vorbis stream may not be seekable.\n";
+  }
 
 
   _last_seek = ov_time_tell(&_ov);
   _last_seek = ov_time_tell(&_ov);
   _samples_read = 0;
   _samples_read = 0;
@@ -114,7 +141,7 @@ seek(double t) {
  * read.  Your buffer must be equal in size to N * channels.  Multiple-channel
  * read.  Your buffer must be equal in size to N * channels.  Multiple-channel
  * audio will be interleaved.
  * audio will be interleaved.
  */
  */
-void VorbisAudioCursor::
+int VorbisAudioCursor::
 read_samples(int n, int16_t *data) {
 read_samples(int n, int16_t *data) {
   int desired = n * _audio_channels;
   int desired = n * _audio_channels;
 
 
@@ -131,6 +158,9 @@ read_samples(int n, int16_t *data) {
       buffer += read_bytes;
       buffer += read_bytes;
       length -= read_bytes;
       length -= read_bytes;
     } else {
     } else {
+      if (read_bytes == 0 && _length == 1.0E10) {
+        _length = ov_time_tell(&_ov);
+      }
       break;
       break;
     }
     }
 
 
@@ -159,6 +189,7 @@ read_samples(int n, int16_t *data) {
   }
   }
 
 
   _samples_read += n;
   _samples_read += n;
+  return n;
 }
 }
 
 
 /**
 /**
@@ -199,6 +230,22 @@ cb_seek_func(void *datasource, ogg_int64_t offset, int whence) {
     break;
     break;
 
 
   case SEEK_CUR:
   case SEEK_CUR:
+    // Vorbis uses a seek with offset 0 to determine whether seeking is
+    // supported, but this is not good enough.  We seek to the end and back.
+    if (offset == 0) {
+      std::streambuf *buf = stream->rdbuf();
+      std::streampos pos = buf->pubseekoff(0, std::ios::cur, std::ios::in);
+      if (pos < 0) {
+        return -1;
+      }
+      if (buf->pubseekoff(0, std::ios::end, std::ios::in) >= 0) {
+        // It worked; seek back to the previous location.
+        buf->pubseekpos(pos, std::ios::in);
+        return 0;
+      } else {
+        return -1;
+      }
+    }
     stream->seekg(offset, std::ios::cur);
     stream->seekg(offset, std::ios::cur);
     break;
     break;
 
 

+ 1 - 10
panda/src/movies/vorbisAudioCursor.h

@@ -35,7 +35,7 @@ PUBLISHED:
   virtual void seek(double offset);
   virtual void seek(double offset);
 
 
 public:
 public:
-  virtual void read_samples(int n, int16_t *data);
+  virtual int read_samples(int n, int16_t *data);
 
 
   bool _is_valid;
   bool _is_valid;
 
 
@@ -50,16 +50,7 @@ protected:
 #ifndef CPPPARSER
 #ifndef CPPPARSER
   OggVorbis_File _ov;
   OggVorbis_File _ov;
 #endif
 #endif
-
   int _bitstream;
   int _bitstream;
-  double _byte_rate;
-  int _block_align;
-  int _bytes_per_sample;
-  bool _is_float;
-
-  std::streampos _data_start;
-  std::streampos _data_pos;
-  size_t _data_size;
 
 
 public:
 public:
   static TypeHandle get_class_type() {
   static TypeHandle get_class_type() {

+ 46 - 10
panda/src/movies/wavAudioCursor.cxx

@@ -294,27 +294,61 @@ seek(double t) {
   t = std::max(t, 0.0);
   t = std::max(t, 0.0);
   std::streampos pos = _data_start + (std::streampos) std::min((size_t) (t * _byte_rate), _data_size);
   std::streampos pos = _data_start + (std::streampos) std::min((size_t) (t * _byte_rate), _data_size);
 
 
+  std::streambuf *buf = _stream->rdbuf();
+
   if (_can_seek_fast) {
   if (_can_seek_fast) {
-    _stream->seekg(pos);
-    if (_stream->tellg() != pos) {
+    if (buf->pubseekpos(pos, std::ios::in) != pos) {
       // Clearly, we can't seek fast.  Fall back to the case below.
       // Clearly, we can't seek fast.  Fall back to the case below.
       _can_seek_fast = false;
       _can_seek_fast = false;
     }
     }
   }
   }
 
 
-  if (!_can_seek_fast) {
-    std::streampos current = _stream->tellg();
+  // Get the current position of the cursor in the file.
+  std::streampos current = buf->pubseekoff(0, std::ios::cur, std::ios::in);
 
 
+  if (!_can_seek_fast) {
     if (pos > current) {
     if (pos > current) {
       // It is ahead of our current position.  Skip ahead.
       // It is ahead of our current position.  Skip ahead.
-      _reader.skip_bytes(pos - current);
+      _stream->ignore(pos - current);
+      current = pos;
 
 
     } else if (pos < current) {
     } else if (pos < current) {
-      // We'll have to reopen the file.  TODO
+      // Can we seek to the beginning?  Some streams, such as ZStream, let us
+      // rewind the stream.
+      if (buf->pubseekpos(0, std::ios::in) == (std::streampos)0) {
+        if (pos > _data_start && movies_cat.is_info()) {
+          Filename fn = get_source()->get_filename();
+          movies_cat.info()
+            << "Unable to seek backwards in " << fn.get_basename()
+            << "; seeking to beginning and skipping " << pos << " bytes.\n";
+        }
+        _stream->ignore(pos);
+        current = pos;
+      } else {
+        // No; close and reopen the file.
+        Filename fn = get_source()->get_filename();
+        movies_cat.warning()
+          << "Unable to seek backwards in " << fn.get_basename()
+          << "; reopening and skipping " << pos << " bytes.\n";
+
+        VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
+        std::istream *stream = vfs->open_read_file(get_source()->get_filename(), true);
+        if (stream != nullptr) {
+          vfs->close_read_file(_stream);
+          stream->ignore(pos);
+          _stream = stream;
+          _reader = StreamReader(stream, false);
+          current = pos;
+        } else {
+          movies_cat.error()
+            << "Unable to reopen " << fn << ".\n";
+          _can_seek = false;
+        }
+      }
     }
     }
   }
   }
 
 
-  _data_pos = _stream->tellg() - _data_start;
+  _data_pos = (size_t)current - _data_start;
   _last_seek = _data_pos / _byte_rate;
   _last_seek = _data_pos / _byte_rate;
   _samples_read = 0;
   _samples_read = 0;
 }
 }
@@ -324,13 +358,13 @@ seek(double t) {
  * read.  Your buffer must be equal in size to N * channels.  Multiple-channel
  * read.  Your buffer must be equal in size to N * channels.  Multiple-channel
  * audio will be interleaved.
  * audio will be interleaved.
  */
  */
-void WavAudioCursor::
+int WavAudioCursor::
 read_samples(int n, int16_t *data) {
 read_samples(int n, int16_t *data) {
   int desired = n * _audio_channels;
   int desired = n * _audio_channels;
   int read_samples = std::min(desired, ((int) (_data_size - _data_pos)) / _bytes_per_sample);
   int read_samples = std::min(desired, ((int) (_data_size - _data_pos)) / _bytes_per_sample);
 
 
   if (read_samples <= 0) {
   if (read_samples <= 0) {
-    return;
+    return 0;
   }
   }
 
 
   switch (_format) {
   switch (_format) {
@@ -421,8 +455,10 @@ read_samples(int n, int16_t *data) {
   // Fill the rest of the buffer with silence.
   // Fill the rest of the buffer with silence.
   if (read_samples < desired) {
   if (read_samples < desired) {
     memset(data + read_samples, 0, (desired - read_samples) * 2);
     memset(data + read_samples, 0, (desired - read_samples) * 2);
+    n = read_samples / _audio_channels;
   }
   }
 
 
   _data_pos = _stream->tellg() - _data_start;
   _data_pos = _stream->tellg() - _data_start;
-  _samples_read += read_samples / _audio_channels;
+  _samples_read += n;
+  return n;
 }
 }

+ 1 - 1
panda/src/movies/wavAudioCursor.h

@@ -31,7 +31,7 @@ PUBLISHED:
   virtual void seek(double offset);
   virtual void seek(double offset);
 
 
 public:
 public:
-  virtual void read_samples(int n, int16_t *data);
+  virtual int read_samples(int n, int16_t *data);
 
 
   bool _is_valid;
   bool _is_valid;
 
 

+ 1 - 0
panda/src/ode/odeBody.h

@@ -133,6 +133,7 @@ PUBLISHED:
   OdeJoint get_joint(int index) const;
   OdeJoint get_joint(int index) const;
   MAKE_SEQ(get_joints, get_num_joints, get_joint);
   MAKE_SEQ(get_joints, get_num_joints, get_joint);
   EXTENSION(INLINE PyObject *get_converted_joint(int i) const);
   EXTENSION(INLINE PyObject *get_converted_joint(int i) const);
+  MAKE_SEQ_PROPERTY(joints, get_num_joints, get_converted_joint);
 
 
   INLINE void enable();
   INLINE void enable();
   INLINE void disable();
   INLINE void disable();

+ 1 - 1
panda/src/ode/odeJoint.h

@@ -83,7 +83,7 @@ PUBLISHED:
   INLINE void set_feedback(bool flag = true);
   INLINE void set_feedback(bool flag = true);
   INLINE OdeJointFeedback *get_feedback();
   INLINE OdeJointFeedback *get_feedback();
 
 
-  EXTENSION(void attach(const OdeBody *body1, const OdeBody *body2));
+  EXTENSION(void attach(PyObject *body1, PyObject *body2));
   void attach_bodies(const OdeBody &body1, const OdeBody &body2);
   void attach_bodies(const OdeBody &body1, const OdeBody &body2);
   void attach_body(const OdeBody &body, int index);
   void attach_body(const OdeBody &body, int index);
   void detach();
   void detach();

+ 18 - 1
panda/src/ode/odeJoint_ext.cxx

@@ -29,6 +29,7 @@
 #include "odePlane2dJoint.h"
 #include "odePlane2dJoint.h"
 
 
 #ifndef CPPPARSER
 #ifndef CPPPARSER
+extern Dtool_PyTypedObject Dtool_OdeBody;
 extern Dtool_PyTypedObject Dtool_OdeJoint;
 extern Dtool_PyTypedObject Dtool_OdeJoint;
 extern Dtool_PyTypedObject Dtool_OdeBallJoint;
 extern Dtool_PyTypedObject Dtool_OdeBallJoint;
 extern Dtool_PyTypedObject Dtool_OdeHingeJoint;
 extern Dtool_PyTypedObject Dtool_OdeHingeJoint;
@@ -48,7 +49,23 @@ extern Dtool_PyTypedObject Dtool_OdePlane2dJoint;
  * attached to the environment.
  * attached to the environment.
  */
  */
 void Extension<OdeJoint>::
 void Extension<OdeJoint>::
-attach(const OdeBody *body1, const OdeBody *body2) {
+attach(PyObject *param1, PyObject *param2) {
+  const OdeBody *body1 = nullptr;
+  if (param1 != Py_None) {
+    body1 = (const OdeBody *)DTOOL_Call_GetPointerThisClass(param1, &Dtool_OdeBody, 1, "OdeJoint.attach", true, true);
+    if (body1 == nullptr) {
+      return;
+    }
+  }
+
+  const OdeBody *body2 = nullptr;
+  if (param2 != Py_None) {
+    body2 = (const OdeBody *)DTOOL_Call_GetPointerThisClass(param2, &Dtool_OdeBody, 2, "OdeJoint.attach", true, true);
+    if (body2 == nullptr) {
+      return;
+    }
+  }
+
   if (body1 && body2) {
   if (body1 && body2) {
     _this->attach_bodies(*body1, *body2);
     _this->attach_bodies(*body1, *body2);
 
 

+ 1 - 1
panda/src/ode/odeJoint_ext.h

@@ -30,7 +30,7 @@
 template<>
 template<>
 class Extension<OdeJoint> : public ExtensionBase<OdeJoint> {
 class Extension<OdeJoint> : public ExtensionBase<OdeJoint> {
 public:
 public:
-  void attach(const OdeBody *body1, const OdeBody *body2);
+  void attach(PyObject *body1, PyObject *body2);
 
 
   PyObject *convert() const;
   PyObject *convert() const;
 };
 };

+ 51 - 0
panda/src/pgraph/shaderAttrib.cxx

@@ -422,6 +422,57 @@ get_shader_input_ptr(const InternalName *id) const {
   }
   }
 }
 }
 
 
+/**
+ * Returns the ShaderInput as a ShaderPtrData struct.  Assertion fails if
+ * there is none.  or if it is not a PTA(double/float)
+ */
+bool ShaderAttrib::
+get_shader_input_ptr(const InternalName *id, Shader::ShaderPtrData &data) const {
+  Inputs::const_iterator i = _inputs.find(id);
+  if (i != _inputs.end()) {
+    const ShaderInput &p = (*i).second;
+    if (p.get_value_type() == ShaderInput::M_numeric ||
+        p.get_value_type() == ShaderInput::M_vector) {
+
+      data = p.get_ptr();
+      return (data._ptr != nullptr);
+    }
+    if (p.get_value_type() == ShaderInput::M_param) {
+      // Temporary solution until the new param system
+      TypedWritableReferenceCount *param = p.get_value();
+      if (param != nullptr) {
+        if (param->is_of_type(ParamVecBase4f::get_class_type())) {
+          data._ptr = (void *)((const ParamVecBase4f *)param)->get_value().get_data();
+          data._size = 4;
+          data._type = Shader::SPT_float;
+          return true;
+        }
+        else if (param->is_of_type(ParamVecBase4i::get_class_type())) {
+          data._ptr = (void *)((const ParamVecBase4i *)param)->get_value().get_data();
+          data._size = 4;
+          data._type = Shader::SPT_int;
+          return true;
+        }
+        else if (param->is_of_type(ParamVecBase4d::get_class_type())) {
+          data._ptr = (void *)((const ParamVecBase4d *)param)->get_value().get_data();
+          data._size = 4;
+          data._type = Shader::SPT_float;
+          return true;
+        }
+      }
+    }
+    ostringstream strm;
+    strm << "Shader input " << id->get_name() << " was given an incompatible parameter type.\n";
+    nassert_raise(strm.str());
+    return false;
+  } else {
+    ostringstream strm;
+    strm << "Shader input " << id->get_name() << " is not present.\n";
+    nassert_raise(strm.str());
+    return false;
+  }
+}
+
 /**
 /**
  * Returns the ShaderInput as a texture.  Assertion fails if there is none, or
  * Returns the ShaderInput as a texture.  Assertion fails if there is none, or
  * if it is not a texture.
  * if it is not a texture.

+ 1 - 0
panda/src/pgraph/shaderAttrib.h

@@ -119,6 +119,7 @@ PUBLISHED:
   LVecBase4 get_shader_input_vector(InternalName *id) const;
   LVecBase4 get_shader_input_vector(InternalName *id) const;
   Texture *get_shader_input_texture(const InternalName *id, SamplerState *sampler=nullptr) const;
   Texture *get_shader_input_texture(const InternalName *id, SamplerState *sampler=nullptr) const;
   const Shader::ShaderPtrData *get_shader_input_ptr(const InternalName *id) const;
   const Shader::ShaderPtrData *get_shader_input_ptr(const InternalName *id) const;
+  bool get_shader_input_ptr(const InternalName *id, Shader::ShaderPtrData &data) const;
   const LMatrix4 &get_shader_input_matrix(const InternalName *id, LMatrix4 &matrix) const;
   const LMatrix4 &get_shader_input_matrix(const InternalName *id, LMatrix4 &matrix) const;
   ShaderBuffer *get_shader_input_buffer(const InternalName *id) const;
   ShaderBuffer *get_shader_input_buffer(const InternalName *id) const;
 
 

+ 5 - 1
panda/src/pgui/pgButton.cxx

@@ -115,7 +115,11 @@ release(const MouseWatcherParameter &param, bool background) {
   if (has_click_button(param.get_button())) {
   if (has_click_button(param.get_button())) {
     _button_down = false;
     _button_down = false;
     if (get_active()) {
     if (get_active()) {
-      if (param.is_outside()) {
+      // Note that a "click" may come from a keyboard button press.  In that
+      // case, instead of checking that the mouse cursor is still over the
+      // button, we check whether the item has keyboard focus.
+      if (param.is_outside() &&
+          (MouseButton::is_mouse_button(param.get_button()) || !get_focus())) {
         set_state(S_ready);
         set_state(S_ready);
       } else {
       } else {
         set_state(S_rollover);
         set_state(S_rollover);

+ 2 - 0
panda/src/pipeline/mutexDebug.I

@@ -70,6 +70,8 @@ acquire(Thread *current_thread) const {
 /**
 /**
  * Returns immediately, with a true value indicating the mutex has been
  * Returns immediately, with a true value indicating the mutex has been
  * acquired, and false indicating it has not.
  * acquired, and false indicating it has not.
+ *
+ * @deprecated Python users should use acquire(False), C++ users try_lock()
  */
  */
 INLINE bool MutexDebug::
 INLINE bool MutexDebug::
 try_acquire(Thread *current_thread) const {
 try_acquire(Thread *current_thread) const {

+ 2 - 0
panda/src/pipeline/mutexDirect.I

@@ -60,6 +60,8 @@ acquire() const {
 /**
 /**
  * Returns immediately, with a true value indicating the mutex has been
  * Returns immediately, with a true value indicating the mutex has been
  * acquired, and false indicating it has not.
  * acquired, and false indicating it has not.
+ *
+ * @deprecated Python users should use acquire(False), C++ users try_lock()
  */
  */
 INLINE bool MutexDirect::
 INLINE bool MutexDirect::
 try_acquire() const {
 try_acquire() const {

+ 4 - 0
panda/src/pipeline/pmutex.h

@@ -49,6 +49,10 @@ PUBLISHED:
 
 
   void operator = (const Mutex &copy) = delete;
   void operator = (const Mutex &copy) = delete;
 
 
+  EXTENSION(bool acquire(bool blocking=true) const);
+  EXTENSION(bool __enter__());
+  EXTENSION(void __exit__(PyObject *, PyObject *, PyObject *));
+
 public:
 public:
   // This is a global mutex set aside for the purpose of protecting Notify
   // This is a global mutex set aside for the purpose of protecting Notify
   // messages from being interleaved between threads.
   // messages from being interleaved between threads.

+ 53 - 0
panda/src/pipeline/pmutex_ext.I

@@ -0,0 +1,53 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file pmutex_ext.h
+ * @author rdb
+ * @date 2019-05-12
+ */
+
+/**
+ * Acquires the mutex.
+ */
+INLINE bool Extension<Mutex>::
+acquire(bool blocking) const {
+  if (_this->try_lock()) {
+    return true;
+  }
+
+  if (!blocking) {
+    return false;
+  }
+
+  // Release the GIL while we are waiting for the lock.
+#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
+  PyThreadState *_save;
+  Py_UNBLOCK_THREADS
+  _this->lock();
+  Py_BLOCK_THREADS
+#else
+  _this->lock();
+#endif
+  return true;
+}
+
+/**
+ * Acquires the mutex.
+ */
+INLINE bool Extension<Mutex>::
+__enter__() {
+  return acquire(true);
+}
+
+/**
+ * Releases the mutex.
+ */
+INLINE void Extension<Mutex>::
+__exit__(PyObject *, PyObject *, PyObject *) {
+  _this->unlock();
+}

+ 41 - 0
panda/src/pipeline/pmutex_ext.h

@@ -0,0 +1,41 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file pmutex_ext.h
+ * @author rdb
+ * @date 2019-05-12
+ */
+
+#ifndef PMUTEX_EXT_H
+#define PMUTEX_EXT_H
+
+#include "dtoolbase.h"
+
+#ifdef HAVE_PYTHON
+
+#include "extension.h"
+#include "pmutex.h"
+#include "py_panda.h"
+
+/**
+ * This class defines the extension methods for Mutex, which are called
+ * instead of any C++ methods with the same prototype.
+ */
+template<>
+class Extension<Mutex> : public ExtensionBase<Mutex> {
+public:
+  INLINE bool acquire(bool blocking) const;
+  INLINE bool __enter__();
+  INLINE void __exit__(PyObject *, PyObject *, PyObject *);
+};
+
+#include "pmutex_ext.I"
+
+#endif  // HAVE_PYTHON
+
+#endif  // PMUTEX_EXT_H

+ 4 - 0
panda/src/pipeline/reMutex.h

@@ -42,6 +42,10 @@ PUBLISHED:
   ~ReMutex() = default;
   ~ReMutex() = default;
 
 
   void operator = (const ReMutex &copy) = delete;
   void operator = (const ReMutex &copy) = delete;
+
+  EXTENSION(bool acquire(bool blocking=true) const);
+  EXTENSION(bool __enter__());
+  EXTENSION(void __exit__(PyObject *, PyObject *, PyObject *));
 };
 };
 
 
 #include "reMutex.I"
 #include "reMutex.I"

+ 4 - 0
panda/src/pipeline/reMutexDirect.I

@@ -105,6 +105,8 @@ acquire(Thread *current_thread) const {
 /**
 /**
  * Returns immediately, with a true value indicating the mutex has been
  * Returns immediately, with a true value indicating the mutex has been
  * acquired, and false indicating it has not.
  * acquired, and false indicating it has not.
+ *
+ * @deprecated Python users should use acquire(False), C++ users try_lock()
  */
  */
 INLINE bool ReMutexDirect::
 INLINE bool ReMutexDirect::
 try_acquire() const {
 try_acquire() const {
@@ -119,6 +121,8 @@ try_acquire() const {
 /**
 /**
  * Returns immediately, with a true value indicating the mutex has been
  * Returns immediately, with a true value indicating the mutex has been
  * acquired, and false indicating it has not.
  * acquired, and false indicating it has not.
+ *
+ * @deprecated Python users should use acquire(False), C++ users try_lock()
  */
  */
 INLINE bool ReMutexDirect::
 INLINE bool ReMutexDirect::
 try_acquire(Thread *current_thread) const {
 try_acquire(Thread *current_thread) const {

+ 53 - 0
panda/src/pipeline/reMutex_ext.I

@@ -0,0 +1,53 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file pmutex_ext.h
+ * @author rdb
+ * @date 2019-05-12
+ */
+
+/**
+ * Acquires the mutex.
+ */
+INLINE bool Extension<ReMutex>::
+acquire(bool blocking) const {
+  if (_this->try_lock()) {
+    return true;
+  }
+
+  if (!blocking) {
+    return false;
+  }
+
+  // Release the GIL while we are waiting for the lock.
+#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
+  PyThreadState *_save;
+  Py_UNBLOCK_THREADS
+  _this->lock();
+  Py_BLOCK_THREADS
+#else
+  _this->lock();
+#endif
+  return true;
+}
+
+/**
+ * Acquires the mutex.
+ */
+INLINE bool Extension<ReMutex>::
+__enter__() {
+  return acquire(true);
+}
+
+/**
+ * Releases the mutex.
+ */
+INLINE void Extension<ReMutex>::
+__exit__(PyObject *, PyObject *, PyObject *) {
+  _this->unlock();
+}

+ 41 - 0
panda/src/pipeline/reMutex_ext.h

@@ -0,0 +1,41 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file remutex_ext.h
+ * @author rdb
+ * @date 2019-05-12
+ */
+
+#ifndef REMUTEX_EXT_H
+#define REMUTEX_EXT_H
+
+#include "dtoolbase.h"
+
+#ifdef HAVE_PYTHON
+
+#include "extension.h"
+#include "reMutex.h"
+#include "py_panda.h"
+
+/**
+ * This class defines the extension methods for ReMutex, which are called
+ * instead of any C++ methods with the same prototype.
+ */
+template<>
+class Extension<ReMutex> : public ExtensionBase<ReMutex> {
+public:
+  INLINE bool acquire(bool blocking) const;
+  INLINE bool __enter__();
+  INLINE void __exit__(PyObject *, PyObject *, PyObject *);
+};
+
+#include "reMutex_ext.I"
+
+#endif  // HAVE_PYTHON
+
+#endif  // REMUTEX_EXT_H

+ 8 - 7
panda/src/putil/sparseArray.cxx

@@ -262,8 +262,8 @@ compare_to(const SparseArray &other) const {
       return -1;
       return -1;
     }
     }
 
 
-    --ai;
-    --bi;
+    ++ai;
+    ++bi;
   }
   }
 
 
   if (ai != _subranges.rend()) {
   if (ai != _subranges.rend()) {
@@ -440,9 +440,9 @@ do_remove_range(int begin, int end) {
   if (si == _subranges.end()) {
   if (si == _subranges.end()) {
     if (!_subranges.empty()) {
     if (!_subranges.empty()) {
       si = _subranges.begin() + _subranges.size() - 1;
       si = _subranges.begin() + _subranges.size() - 1;
-      if ((*si)._end >= begin) {
+      if ((*si)._end > begin) {
         // The new range shortens the last element of the array on the right.
         // The new range shortens the last element of the array on the right.
-        end = std::min(end, (*si)._begin);
+        end = std::max(begin, (*si)._begin);
         (*si)._end = end;
         (*si)._end = end;
         // It might also shorten it on the left; fall through.
         // It might also shorten it on the left; fall through.
       } else {
       } else {
@@ -462,10 +462,10 @@ do_remove_range(int begin, int end) {
     if (si != _subranges.begin()) {
     if (si != _subranges.begin()) {
       Subranges::iterator si2 = si;
       Subranges::iterator si2 = si;
       --si2;
       --si2;
-      if ((*si2)._end >= begin) {
+      if ((*si2)._end > begin) {
         // The new range shortens an element within the array on the right
         // The new range shortens an element within the array on the right
         // (but does not intersect the next element).
         // (but does not intersect the next element).
-        end = std::min(end, (*si2)._begin);
+        end = std::max(begin, (*si2)._begin);
         (*si2)._end = end;
         (*si2)._end = end;
         // It might also shorten it on the left; fall through.
         // It might also shorten it on the left; fall through.
         si = si2;
         si = si2;
@@ -488,7 +488,7 @@ do_remove_range(int begin, int end) {
   }
   }
 
 
   // Check if the new range removes any elements to the left.
   // Check if the new range removes any elements to the left.
-  while (begin <= (*si)._begin) {
+  while (begin <= (*si)._begin || (*si)._begin >= (*si)._end) {
     if (si == _subranges.begin()) {
     if (si == _subranges.begin()) {
       _subranges.erase(si);
       _subranges.erase(si);
       return;
       return;
@@ -500,6 +500,7 @@ do_remove_range(int begin, int end) {
   }
   }
 
 
   (*si)._end = std::min((*si)._end, begin);
   (*si)._end = std::min((*si)._end, begin);
+  nassertv((*si)._end > (*si)._begin);
 }
 }
 
 
 /**
 /**

+ 12 - 1
panda/src/wgldisplay/wglGraphicsStateGuardian.cxx

@@ -609,9 +609,20 @@ make_context(HDC hdc) {
         attrib_list[n++] = gl_version[1];
         attrib_list[n++] = gl_version[1];
       }
       }
     }
     }
+    int flags = 0;
     if (gl_debug) {
     if (gl_debug) {
+      flags |= WGL_CONTEXT_DEBUG_BIT_ARB;
+    }
+    if (gl_forward_compatible) {
+      flags |= WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB;
+      if (gl_version.get_num_words() == 0 || gl_version[0] < 2) {
+        wgldisplay_cat.error()
+          << "gl-forward-compatible requires gl-version >= 3 0\n";
+      }
+    }
+    if (flags != 0) {
       attrib_list[n++] = WGL_CONTEXT_FLAGS_ARB;
       attrib_list[n++] = WGL_CONTEXT_FLAGS_ARB;
-      attrib_list[n++] = WGL_CONTEXT_DEBUG_BIT_ARB;
+      attrib_list[n++] = flags;
     }
     }
 #ifndef SUPPORT_FIXED_FUNCTION
 #ifndef SUPPORT_FIXED_FUNCTION
     attrib_list[n++] = WGL_CONTEXT_PROFILE_MASK_ARB;
     attrib_list[n++] = WGL_CONTEXT_PROFILE_MASK_ARB;

+ 5 - 0
panda/src/windisplay/config_windisplay.cxx

@@ -86,6 +86,11 @@ ConfigVariableBool swapbuffer_framelock
 ("swapbuffer-framelock", false,
 ("swapbuffer-framelock", false,
  PRC_DESC("Set this true to enable HW swapbuffer frame-lock on 3dlabs cards"));
  PRC_DESC("Set this true to enable HW swapbuffer frame-lock on 3dlabs cards"));
 
 
+ConfigVariableBool paste_emit_keystrokes
+("paste-emit-keystrokes", true,
+ PRC_DESC("Handle paste events (Ctrl-V) as separate keystroke events for each "
+          "pasted character."));
+
 /**
 /**
  * Initializes the library.  This must be called at least once before any of
  * Initializes the library.  This must be called at least once before any of
  * the functions or classes in this library can be used.  Normally it will be
  * the functions or classes in this library can be used.  Normally it will be

+ 1 - 0
panda/src/windisplay/config_windisplay.h

@@ -31,6 +31,7 @@ extern ConfigVariableBool ime_hide;
 extern ConfigVariableBool request_dxdisplay_information;
 extern ConfigVariableBool request_dxdisplay_information;
 extern ConfigVariableBool dpi_aware;
 extern ConfigVariableBool dpi_aware;
 extern ConfigVariableBool dpi_window_resize;
 extern ConfigVariableBool dpi_window_resize;
+extern ConfigVariableBool paste_emit_keystrokes;
 
 
 extern EXPCL_PANDAWIN ConfigVariableBool swapbuffer_framelock;
 extern EXPCL_PANDAWIN ConfigVariableBool swapbuffer_framelock;
 
 

+ 1 - 1
panda/src/windisplay/winGraphicsWindow.cxx

@@ -1927,7 +1927,7 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
       // Handle Cntrl-V paste from clipboard.  Is there a better way to detect
       // Handle Cntrl-V paste from clipboard.  Is there a better way to detect
       // this hotkey?
       // this hotkey?
       if ((wparam=='V') && (GetKeyState(VK_CONTROL) < 0) &&
       if ((wparam=='V') && (GetKeyState(VK_CONTROL) < 0) &&
-          !_input_devices.empty()) {
+          !_input_devices.empty() && paste_emit_keystrokes) {
         HGLOBAL hglb;
         HGLOBAL hglb;
         char *lptstr;
         char *lptstr;
 
 

+ 8 - 30
panda/src/x11display/x11GraphicsWindow.cxx

@@ -589,7 +589,13 @@ set_properties_now(WindowProperties &properties) {
       // OK, first figure out which CRTC the window is on.  It may be on more
       // OK, first figure out which CRTC the window is on.  It may be on more
       // than one, actually, so grab a point in the center in order to figure
       // than one, actually, so grab a point in the center in order to figure
       // out which one it's more-or-less mostly on.
       // out which one it's more-or-less mostly on.
-      LPoint2i center = _properties.get_origin() + _properties.get_size() / 2;
+      LPoint2i center(0, 0);
+      if (_properties.has_origin()) {
+        center = _properties.get_origin();
+        if (_properties.has_size()) {
+          center += _properties.get_size() / 2;
+        }
+      }
       int x, y, width, height;
       int x, y, width, height;
       x11_pipe->find_fullscreen_crtc(center, x, y, width, height);
       x11_pipe->find_fullscreen_crtc(center, x, y, width, height);
 
 
@@ -628,7 +634,7 @@ set_properties_now(WindowProperties &properties) {
         // We may need to change the screen resolution.  The code below is
         // We may need to change the screen resolution.  The code below is
         // suboptimal; in the future, we probably want to only touch the CRTC
         // suboptimal; in the future, we probably want to only touch the CRTC
         // that the window is on.
         // that the window is on.
-        XRRScreenConfiguration *conf = _XRRGetScreenInfo(_display, _xwindow);
+        XRRScreenConfiguration *conf = _XRRGetScreenInfo(_display, _xwindow ? _xwindow : x11_pipe->get_root());
         SizeID old_size_id = x11_pipe->_XRRConfigCurrentConfiguration(conf, &_orig_rotation);
         SizeID old_size_id = x11_pipe->_XRRConfigCurrentConfiguration(conf, &_orig_rotation);
         SizeID new_size_id = (SizeID) -1;
         SizeID new_size_id = (SizeID) -1;
         int num_sizes = 0;
         int num_sizes = 0;
@@ -1010,34 +1016,6 @@ open_window() {
   // Make sure we are not making X11 calls from other threads.
   // Make sure we are not making X11 calls from other threads.
   LightReMutexHolder holder(x11GraphicsPipe::_x_mutex);
   LightReMutexHolder holder(x11GraphicsPipe::_x_mutex);
 
 
-  if (_properties.get_fullscreen() && x11_pipe->_have_xrandr) {
-    XRRScreenConfiguration* conf = _XRRGetScreenInfo(_display, x11_pipe->get_root());
-    if (_orig_size_id == (SizeID) -1) {
-      _orig_size_id = x11_pipe->_XRRConfigCurrentConfiguration(conf, &_orig_rotation);
-    }
-    int num_sizes, new_size_id = -1;
-    XRRScreenSize *xrrs;
-    xrrs = x11_pipe->_XRRSizes(_display, 0, &num_sizes);
-    for (int i = 0; i < num_sizes; ++i) {
-      if (xrrs[i].width == _properties.get_x_size() &&
-          xrrs[i].height == _properties.get_y_size()) {
-        new_size_id = i;
-      }
-    }
-    if (new_size_id == -1) {
-      x11display_cat.error()
-        << "Videocard has no supported display resolutions at specified res ("
-        << _properties.get_x_size() << " x " << _properties.get_y_size() <<")\n";
-      _orig_size_id = -1;
-      return false;
-    }
-    if (new_size_id != _orig_size_id) {
-      _XRRSetScreenConfig(_display, conf, x11_pipe->get_root(), new_size_id, _orig_rotation, CurrentTime);
-    } else {
-      _orig_size_id = -1;
-    }
-  }
-
   X11_Window parent_window = x11_pipe->get_root();
   X11_Window parent_window = x11_pipe->get_root();
   WindowHandle *window_handle = _properties.get_parent_window();
   WindowHandle *window_handle = _properties.get_parent_window();
   if (window_handle != nullptr) {
   if (window_handle != nullptr) {

+ 45 - 0
tests/bullet/test_bullet_heightfield.py

@@ -0,0 +1,45 @@
+import pytest
+# Skip these tests if we can't import bullet.
+bullet = pytest.importorskip("panda3d.bullet")
+
+from panda3d.bullet import BulletWorld, BulletRigidBodyNode, ZUp
+from panda3d.bullet import BulletHeightfieldShape, BulletSphereShape
+from panda3d.core import NodePath, PNMImage
+
+
+def make_node(name, BulletShape, *args):
+    # Returns a BulletRigidBodyNode for the given shape
+    shape = BulletShape(*args)
+    node = BulletRigidBodyNode(name)
+    node.add_shape(shape)
+    return node
+
+
+def test_sphere_into_heightfield():
+    root = NodePath("root")
+    world = BulletWorld()
+    # Create PNMImage to construct Heightfield with
+    img = PNMImage(10, 10, 1)
+    img.fill_val(255)
+    # Make our nodes
+    heightfield = make_node("Heightfield", BulletHeightfieldShape, img, 1, ZUp)
+    sphere = make_node("Sphere", BulletSphereShape, 1)
+    # Attach to world
+    np1 = root.attach_new_node(sphere)
+    np1.set_pos(0, 0, 1)
+    world.attach(sphere)
+
+    np2 = root.attach_new_node(heightfield)
+    np2.set_pos(0, 0, 0)
+    world.attach(heightfield)
+
+    assert world.get_num_rigid_bodies() == 2
+    test = world.contact_test_pair(sphere, heightfield)
+    assert test.get_num_contacts() > 0
+    assert test.get_contact(0).get_node0() == sphere
+    assert test.get_contact(0).get_node1() == heightfield
+
+    # Increment sphere's Z coordinate, no longer colliding
+    np1.set_pos(0, 0, 2)
+    test = world.contact_test_pair(sphere, heightfield)
+    assert test.get_num_contacts() == 0

+ 30 - 0
tests/display/test_glsl_shader.py

@@ -294,6 +294,36 @@ def test_glsl_pta_mat4(gsg):
     run_glsl_test(gsg, code, preamble, {'pta': pta})
     run_glsl_test(gsg, code, preamble, {'pta': pta})
 
 
 
 
+def test_glsl_param_vec4(gsg):
+    param = core.ParamVecBase4((0, 1, 2, 3))
+
+    preamble = """
+    uniform vec4 param;
+    """
+    code = """
+    assert(param.x == 0.0);
+    assert(param.y == 1.0);
+    assert(param.z == 2.0);
+    assert(param.w == 3.0);
+    """
+    run_glsl_test(gsg, code, preamble, {'param': param})
+
+
+def test_glsl_param_ivec4(gsg):
+    param = core.ParamVecBase4i((0, 1, 2, 3))
+
+    preamble = """
+    uniform ivec4 param;
+    """
+    code = """
+    assert(param.x == 0);
+    assert(param.y == 1);
+    assert(param.z == 2);
+    assert(param.w == 3);
+    """
+    run_glsl_test(gsg, code, preamble, {'param': param})
+
+
 def test_glsl_write_extract_image_buffer(gsg):
 def test_glsl_write_extract_image_buffer(gsg):
     # Tests that we can write to a buffer texture on the GPU, and then extract
     # Tests that we can write to a buffer texture on the GPU, and then extract
     # the data on the CPU.  We test two textures since there was in the past a
     # the data on the CPU.  We test two textures since there was in the past a

+ 128 - 0
tests/dtoolutil/test_iostream.py

@@ -0,0 +1,128 @@
+from panda3d.core import StringStream
+
+import pytest
+
+
+ISTREAM_DATA = b'abcdefghijklmnopqrstuvwxyz' * 500
+
[email protected]
+def istream():
+    return StringStream(ISTREAM_DATA)
+
+
+def test_istream_readall(istream):
+    assert istream.readall() == ISTREAM_DATA
+    assert istream.readall() == b''
+    assert istream.readall() == b''
+    assert istream.tellg() == len(ISTREAM_DATA)
+
+
+def test_istream_read(istream):
+    assert istream.read() == ISTREAM_DATA
+    assert istream.read() == b''
+    assert istream.read() == b''
+    assert istream.tellg() == len(ISTREAM_DATA)
+
+
+def test_istream_read_size(istream):
+    assert istream.read(100) == ISTREAM_DATA[:100]
+    assert istream.read(5000) == ISTREAM_DATA[100:5100]
+    assert istream.read(5000) == ISTREAM_DATA[5100:10100]
+    assert istream.read(5000) == ISTREAM_DATA[10100:15100]
+    assert istream.read() == b''
+    assert istream.tellg() == len(ISTREAM_DATA)
+
+
+def test_istream_read1(istream):
+    accumulated = b''
+    data = istream.read1()
+    while data:
+        accumulated += data
+        data = istream.read1()
+
+    assert accumulated == ISTREAM_DATA
+    assert istream.tellg() == len(ISTREAM_DATA)
+
+
+def test_istream_read1_size(istream):
+    accumulated = b''
+    data = istream.read1(4000)
+    while data:
+        accumulated += data
+        data = istream.read1(4000)
+
+    assert accumulated == ISTREAM_DATA
+    assert istream.tellg() == len(ISTREAM_DATA)
+
+
+def test_istream_readinto(istream):
+    ba = bytearray()
+    assert istream.readinto(ba) == 0
+    assert istream.tellg() == 0
+
+    ba = bytearray(10)
+    assert istream.readinto(ba) == 10
+    assert ba == ISTREAM_DATA[:10]
+    assert istream.tellg() == 10
+
+    ba = bytearray(len(ISTREAM_DATA))
+    assert istream.readinto(ba) == len(ISTREAM_DATA) - 10
+    assert ba[:len(ISTREAM_DATA)-10] == ISTREAM_DATA[10:]
+    assert istream.tellg() == len(ISTREAM_DATA)
+
+
+def test_istream_readline():
+    # Empty stream
+    stream = StringStream(b'')
+    assert stream.readline() == b''
+    assert stream.readline() == b''
+
+    # Single line without newline
+    stream = StringStream(b'A')
+    assert stream.readline() == b'A'
+    assert stream.readline() == b''
+
+    # Single newline
+    stream = StringStream(b'\n')
+    assert stream.readline() == b'\n'
+    assert stream.readline() == b''
+
+    # Line with text followed by empty line
+    stream = StringStream(b'A\n\n')
+    assert stream.readline() == b'A\n'
+    assert stream.readline() == b'\n'
+    assert stream.readline() == b''
+
+    # Preserve null byte
+    stream = StringStream(b'\x00\x00')
+    assert stream.readline() == b'\x00\x00'
+
+
+def test_istream_readlines():
+    istream = StringStream(b'a')
+    assert istream.readlines() == [b'a']
+    assert istream.readlines() == []
+
+    istream = StringStream(b'a\nb\nc\n')
+    assert istream.readlines() == [b'a\n', b'b\n', b'c\n']
+
+    istream = StringStream(b'\na\nb\nc')
+    assert istream.readlines() == [b'\n', b'a\n', b'b\n', b'c']
+
+    istream = StringStream(b'\n\n\n')
+    assert istream.readlines() == [b'\n', b'\n', b'\n']
+
+
+def test_istream_iter():
+    istream = StringStream(b'a')
+    assert tuple(istream) == (b'a',)
+    assert tuple(istream) == ()
+
+    istream = StringStream(b'a\nb\nc\n')
+    assert tuple(istream) == (b'a\n', b'b\n', b'c\n')
+
+    istream = StringStream(b'\na\nb\nc')
+    assert tuple(istream) == (b'\n', b'a\n', b'b\n', b'c')
+
+    istream = StringStream(b'\n\n\n')
+    assert tuple(istream) == (b'\n', b'\n', b'\n')

+ 36 - 0
tests/linmath/test_lmatrix4.py

@@ -87,3 +87,39 @@ def test_mat4_invert_correct(type):
 
 
     assert (mat * inv).is_identity()
     assert (mat * inv).is_identity()
     assert (inv * mat).is_identity()
     assert (inv * mat).is_identity()
+
+
[email protected]("type", (core.LMatrix4d, core.LMatrix4f))
+def test_mat4_rows(type):
+    mat = type((1, 2, 3, 4,
+                5, 6, 7, 8,
+                9, 10, 11, 12,
+                13, 14, 15, 16))
+
+    assert mat.rows[0] == (1, 2, 3, 4)
+    assert mat.rows[1] == (5, 6, 7, 8)
+    assert mat.rows[2] == (9, 10, 11, 12)
+    assert mat.rows[3] == (13, 14, 15, 16)
+
+    assert mat.get_row3(0) == (1, 2, 3)
+    assert mat.get_row3(1) == (5, 6, 7)
+    assert mat.get_row3(2) == (9, 10, 11)
+    assert mat.get_row3(3) == (13, 14, 15)
+
+
[email protected]("type", (core.LMatrix4d, core.LMatrix4f))
+def test_mat4_cols(type):
+    mat = type((1, 5, 9, 13,
+                2, 6, 10, 14,
+                3, 7, 11, 15,
+                4, 8, 12, 16))
+
+    assert mat.cols[0] == (1, 2, 3, 4)
+    assert mat.cols[1] == (5, 6, 7, 8)
+    assert mat.cols[2] == (9, 10, 11, 12)
+    assert mat.cols[3] == (13, 14, 15, 16)
+
+    assert mat.get_col3(0) == (1, 2, 3)
+    assert mat.get_col3(1) == (5, 6, 7)
+    assert mat.get_col3(2) == (9, 10, 11)
+    assert mat.get_col3(3) == (13, 14, 15)

Some files were not shown because too many files changed in this diff