Browse Source

Merge branch 'release/1.10.x' into incoming

rdb 5 years ago
parent
commit
05d5bac598

+ 12 - 2
direct/src/actor/Actor.py

@@ -500,7 +500,12 @@ class Actor(DirectObject, NodePath):
 
     def cleanup(self):
         """
-        Actor cleanup function
+        This method should be called when intending to destroy the Actor, and
+        cleans up any additional resources stored on the Actor class before
+        removing the underlying node using `removeNode()`.
+
+        Note that `removeNode()` itself is not sufficient to destroy actors,
+        which is why this method exists.
         """
         self.stop(None)
         self.clearPythonData()
@@ -512,6 +517,11 @@ class Actor(DirectObject, NodePath):
             self.removeNode()
 
     def removeNode(self):
+        """
+        You should call `cleanup()` for Actor objects instead, since
+        :meth:`~panda3d.core.NodePath.removeNode()` is not sufficient for
+        completely destroying Actor objects.
+        """
         if self.__geomNode and (self.__geomNode.getNumChildren() > 0):
             assert self.notify.warning("called actor.removeNode() on %s without calling cleanup()" % self.getName())
         NodePath.removeNode(self)
@@ -525,7 +535,7 @@ class Actor(DirectObject, NodePath):
 
     def flush(self):
         """
-        Actor flush function
+        Actor flush function.  Used by `cleanup()`.
         """
         self.clearPythonData()
 

+ 81 - 16
makepanda/makepanda.py

@@ -797,6 +797,60 @@ if (COMPILER=="GCC"):
         elif os.path.isfile(GetThirdpartyDir() + "ffmpeg/lib/libavcodec.a"):
             # Needed when linking ffmpeg statically on Linux.
             LibName("FFMPEG", "-Wl,-Bsymbolic")
+            # Don't export ffmpeg symbols from libp3ffmpeg when linking statically.
+            for ffmpeg_lib in ffmpeg_libs:
+                LibName("FFMPEG", "-Wl,--exclude-libs,%s.a" % (ffmpeg_lib))
+
+    if GetTarget() != "darwin":
+        for fcollada_lib in fcollada_libs:
+            LibName("FCOLLADA", "-Wl,--exclude-libs,lib%s.a" % (fcollada_lib))
+
+        if not PkgSkip("SWSCALE"):
+            LibName("SWSCALE", "-Wl,--exclude-libs,libswscale.a")
+
+        if not PkgSkip("SWRESAMPLE"):
+            LibName("SWRESAMPLE", "-Wl,--exclude-libs,libswresample.a")
+
+        if not PkgSkip("JPEG"):
+            LibName("JPEG", "-Wl,--exclude-libs,libjpeg.a")
+
+        if not PkgSkip("TIFF"):
+            LibName("TIFF", "-Wl,--exclude-libs,libtiff.a")
+
+        if not PkgSkip("PNG"):
+            LibName("PNG", "-Wl,--exclude-libs,libpng.a")
+            LibName("PNG", "-Wl,--exclude-libs,libpng16.a")
+
+        if not PkgSkip("SQUISH"):
+            LibName("SQUISH", "-Wl,--exclude-libs,libsquish.a")
+
+        if not PkgSkip("OPENEXR"):
+            LibName("OPENEXR", "-Wl,--exclude-libs,libHalf.a")
+            LibName("OPENEXR", "-Wl,--exclude-libs,libIex.a")
+            LibName("OPENEXR", "-Wl,--exclude-libs,libIexMath.a")
+            LibName("OPENEXR", "-Wl,--exclude-libs,libIlmImf.a")
+            LibName("OPENEXR", "-Wl,--exclude-libs,libIlmImfUtil.a")
+            LibName("OPENEXR", "-Wl,--exclude-libs,libIlmThread.a")
+            LibName("OPENEXR", "-Wl,--exclude-libs,libImath.a")
+
+        if not PkgSkip("VORBIS"):
+            LibName("VORBIS", "-Wl,--exclude-libs,libogg.a")
+            LibName("VORBIS", "-Wl,--exclude-libs,libvorbis.a")
+            LibName("VORBIS", "-Wl,--exclude-libs,libvorbisenc.a")
+            LibName("VORBIS", "-Wl,--exclude-libs,libvorbisfile.a")
+
+        if not PkgSkip("OPUS"):
+            LibName("OPUS", "-Wl,--exclude-libs,libogg.a")
+            LibName("OPUS", "-Wl,--exclude-libs,libopus.a")
+            LibName("OPUS", "-Wl,--exclude-libs,libopusfile.a")
+
+        if not PkgSkip("VRPN"):
+            LibName("VRPN", "-Wl,--exclude-libs,libvrpn.a")
+            LibName("VRPN", "-Wl,--exclude-libs,libquat.a")
+
+        if not PkgSkip("ARTOOLKIT"):
+            LibName("ARTOOLKIT", "-Wl,--exclude-libs,libAR.a")
+            LibName("ARTOOLKIT", "-Wl,--exclude-libs,libARMulti.a")
 
     if PkgSkip("FFMPEG") or GetTarget() == "darwin":
         cv_lib = ChooseLib(("opencv_core", "cv"), "OPENCV")
@@ -809,10 +863,13 @@ if (COMPILER=="GCC"):
     else:
         PkgDisable("OPENCV")
 
-    if GetTarget() == "darwin" and not PkgSkip("OPENAL"):
-        LibName("OPENAL", "-framework AudioUnit")
-        LibName("OPENAL", "-framework AudioToolbox")
-        LibName("OPENAL", "-framework CoreAudio")
+    if not PkgSkip("OPENAL"):
+        if GetTarget() == "darwin":
+            LibName("OPENAL", "-framework AudioUnit")
+            LibName("OPENAL", "-framework AudioToolbox")
+            LibName("OPENAL", "-framework CoreAudio")
+        else:
+            LibName("OPENAL", "-Wl,--exclude-libs,libopenal.a")
 
     if not PkgSkip("ASSIMP") and \
         os.path.isfile(GetThirdpartyDir() + "assimp/lib/libassimp.a"):
@@ -821,6 +878,10 @@ if (COMPILER=="GCC"):
         if os.path.isfile(irrxml):
             LibName("ASSIMP", irrxml)
 
+            if GetTarget() != "darwin":
+                LibName("ASSIMP", "-Wl,--exclude-libs,libassimp.a")
+                LibName("ASSIMP", "-Wl,--exclude-libs,libIrrXML.a")
+
     if not PkgSkip("PYTHON"):
         python_lib = SDK["PYTHONVERSION"]
         SmartPkgEnable("PYTHON", "", python_lib, (SDK["PYTHONVERSION"], SDK["PYTHONVERSION"] + "/Python.h"))
@@ -833,6 +894,10 @@ if (COMPILER=="GCC"):
     SmartPkgEnable("ZLIB",      "zlib",      ("z"), "zlib.h")
     SmartPkgEnable("GTK2",      "gtk+-2.0")
 
+    if not PkgSkip("OPENSSL") and GetTarget() != "darwin":
+        LibName("OPENSSL", "-Wl,--exclude-libs,libssl.a")
+        LibName("OPENSSL", "-Wl,--exclude-libs,libcrypto.a")
+
     if GetTarget() != 'darwin':
         # CgGL is covered by the Cg framework, and we don't need X11 components on OSX
         if not PkgSkip("NVIDIACG"):
@@ -3207,7 +3272,7 @@ TargetAdd('interrogate.exe', input='interrogate_composite2.obj')
 TargetAdd('interrogate.exe', input='libp3cppParser.ilb')
 TargetAdd('interrogate.exe', input=COMMON_DTOOL_LIBS)
 TargetAdd('interrogate.exe', input='libp3interrogatedb.dll')
-TargetAdd('interrogate.exe', opts=['ADVAPI',  'OPENSSL', 'WINSHELL', 'WINGDI', 'WINUSER'])
+TargetAdd('interrogate.exe', opts=['ADVAPI', 'WINSHELL', 'WINGDI', 'WINUSER'])
 
 preamble = WriteEmbeddedStringFile('interrogate_preamble_python_native', inputs=[
 'dtool/src/interrogatedb/py_panda.cxx',
@@ -3222,14 +3287,14 @@ TargetAdd('interrogate_module.exe', input='interrogate_module_preamble_python_na
 TargetAdd('interrogate_module.exe', input='libp3cppParser.ilb')
 TargetAdd('interrogate_module.exe', input=COMMON_DTOOL_LIBS)
 TargetAdd('interrogate_module.exe', input='libp3interrogatedb.dll')
-TargetAdd('interrogate_module.exe', opts=['ADVAPI',  'OPENSSL', 'WINSHELL', 'WINGDI', 'WINUSER'])
+TargetAdd('interrogate_module.exe', opts=['ADVAPI', 'WINSHELL', 'WINGDI', 'WINUSER'])
 
 TargetAdd('parse_file_parse_file.obj', opts=OPTS, input='parse_file.cxx')
 TargetAdd('parse_file.exe', input='parse_file_parse_file.obj')
 TargetAdd('parse_file.exe', input='libp3cppParser.ilb')
 TargetAdd('parse_file.exe', input=COMMON_DTOOL_LIBS)
 TargetAdd('parse_file.exe', input='libp3interrogatedb.dll')
-TargetAdd('parse_file.exe', opts=['ADVAPI',  'OPENSSL', 'WINSHELL', 'WINGDI', 'WINUSER'])
+TargetAdd('parse_file.exe', opts=['ADVAPI', 'WINSHELL', 'WINGDI', 'WINUSER'])
 
 #
 # DIRECTORY: dtool/src/prckeys/
@@ -3240,7 +3305,7 @@ if (PkgSkip("OPENSSL")==0):
   TargetAdd('make-prc-key_makePrcKey.obj', opts=OPTS, input='makePrcKey.cxx')
   TargetAdd('make-prc-key.exe', input='make-prc-key_makePrcKey.obj')
   TargetAdd('make-prc-key.exe', input=COMMON_DTOOL_LIBS)
-  TargetAdd('make-prc-key.exe', opts=['ADVAPI',  'OPENSSL', 'WINSHELL', 'WINGDI', 'WINUSER'])
+  TargetAdd('make-prc-key.exe', opts=['ADVAPI', 'OPENSSL', 'WINSHELL', 'WINGDI', 'WINUSER'])
 
 #
 # DIRECTORY: dtool/src/test_interrogate/
@@ -3251,7 +3316,7 @@ TargetAdd('test_interrogate_test_interrogate.obj', opts=OPTS, input='test_interr
 TargetAdd('test_interrogate.exe', input='test_interrogate_test_interrogate.obj')
 TargetAdd('test_interrogate.exe', input='libp3interrogatedb.dll')
 TargetAdd('test_interrogate.exe', input=COMMON_DTOOL_LIBS)
-TargetAdd('test_interrogate.exe', opts=['ADVAPI',  'OPENSSL', 'WINSHELL', 'WINGDI', 'WINUSER'])
+TargetAdd('test_interrogate.exe', opts=['ADVAPI', 'WINSHELL', 'WINGDI', 'WINUSER'])
 
 #
 # DIRECTORY: dtool/src/dtoolbase/
@@ -3352,7 +3417,7 @@ TargetAdd('libpandaexpress.dll', input='p3express_composite1.obj')
 TargetAdd('libpandaexpress.dll', input='p3express_composite2.obj')
 TargetAdd('libpandaexpress.dll', input='p3pandabase_pandabase.obj')
 TargetAdd('libpandaexpress.dll', input=COMMON_DTOOL_LIBS)
-TargetAdd('libpandaexpress.dll', opts=['ADVAPI', 'WINSOCK2',  'OPENSSL', 'ZLIB', 'WINGDI', 'WINUSER', 'ANDROID'])
+TargetAdd('libpandaexpress.dll', opts=['ADVAPI', 'WINSOCK2', 'OPENSSL', 'ZLIB', 'WINGDI', 'WINUSER', 'ANDROID'])
 
 #
 # DIRECTORY: panda/src/pipeline/
@@ -4151,7 +4216,7 @@ if PkgSkip("OPENAL") == 0:
 #
 
 if (PkgSkip("OPENSSL")==0 and PkgSkip("DEPLOYTOOLS")==0):
-  OPTS=['DIR:panda/src/downloadertools', 'OPENSSL', 'ADVAPI', 'WINSOCK2', 'WINSHELL', 'WINGDI', 'WINUSER']
+  OPTS=['DIR:panda/src/downloadertools', 'ADVAPI', 'WINSOCK2', 'WINSHELL', 'WINGDI', 'WINUSER']
 
   TargetAdd('pdecrypt_pdecrypt.obj', opts=OPTS, input='pdecrypt.cxx')
   TargetAdd('pdecrypt.exe', input=['pdecrypt_pdecrypt.obj'])
@@ -4168,7 +4233,7 @@ if (PkgSkip("OPENSSL")==0 and PkgSkip("DEPLOYTOOLS")==0):
 #
 
 if (PkgSkip("ZLIB")==0 and PkgSkip("DEPLOYTOOLS")==0):
-  OPTS=['DIR:panda/src/downloadertools', 'ZLIB', 'OPENSSL', 'ADVAPI', 'WINSOCK2', 'WINSHELL', 'WINGDI', 'WINUSER']
+  OPTS=['DIR:panda/src/downloadertools', 'ZLIB', 'ADVAPI', 'WINSOCK2', 'WINSHELL', 'WINGDI', 'WINUSER']
 
   TargetAdd('multify_multify.obj', opts=OPTS, input='multify.cxx')
   TargetAdd('multify.exe', input=['multify_multify.obj'])
@@ -4748,10 +4813,10 @@ if (PkgSkip("DIRECT")==0):
 #
 
 if (PkgSkip("DIRECT")==0):
-  OPTS=['DIR:direct/src/distributed', 'DIR:direct/src/dcparser', 'WITHINPANDA', 'BUILDING:DIRECT', 'OPENSSL']
+  OPTS=['DIR:direct/src/distributed', 'DIR:direct/src/dcparser', 'WITHINPANDA', 'BUILDING:DIRECT']
   TargetAdd('p3distributed_config_distributed.obj', opts=OPTS, input='config_distributed.cxx')
 
-  OPTS=['DIR:direct/src/distributed', 'WITHINPANDA', 'OPENSSL']
+  OPTS=['DIR:direct/src/distributed', 'WITHINPANDA']
   IGATEFILES=GetDirectoryContents('direct/src/distributed', ["*.h", "*.cxx"])
   TargetAdd('libp3distributed.in', opts=OPTS, input=IGATEFILES)
   TargetAdd('libp3distributed.in', opts=['IMOD:panda3d.direct', 'ILIB:libp3distributed', 'SRCDIR:direct/src/distributed'])
@@ -4819,7 +4884,7 @@ if (PkgSkip("DIRECT")==0):
   TargetAdd('libp3direct.dll', input='p3motiontrail_config_motiontrail.obj')
   TargetAdd('libp3direct.dll', input='p3motiontrail_cMotionTrail.obj')
   TargetAdd('libp3direct.dll', input=COMMON_PANDA_LIBS)
-  TargetAdd('libp3direct.dll', opts=['ADVAPI',  'OPENSSL', 'WINUSER', 'WINGDI'])
+  TargetAdd('libp3direct.dll', opts=['ADVAPI', 'WINUSER', 'WINGDI'])
 
   PyTargetAdd('direct_module.obj', input='libp3dcparser.in')
   PyTargetAdd('direct_module.obj', input='libp3showbase.in')
@@ -4847,7 +4912,7 @@ if (PkgSkip("DIRECT")==0):
   PyTargetAdd('direct.pyd', input='libp3direct.dll')
   PyTargetAdd('direct.pyd', input='libp3interrogatedb.dll')
   PyTargetAdd('direct.pyd', input=COMMON_PANDA_LIBS)
-  PyTargetAdd('direct.pyd', opts=['OPENSSL', 'WINUSER', 'WINGDI', 'WINSOCK2'])
+  PyTargetAdd('direct.pyd', opts=['WINUSER', 'WINGDI', 'WINSOCK2'])
 
 #
 # DIRECTORY: direct/src/dcparse/

+ 2 - 1
panda/src/chan/animChannelScalarTable.h

@@ -30,9 +30,10 @@ protected:
   AnimChannelScalarTable();
   AnimChannelScalarTable(AnimGroup *parent, const AnimChannelScalarTable &copy);
 
-public:
+PUBLISHED:
   AnimChannelScalarTable(AnimGroup *parent, const std::string &name);
 
+public:
   virtual bool has_changed(int last_frame, double last_frac,
                            int this_frame, double this_frac);
   virtual void get_value(int frame, PN_stdfloat &value);

+ 8 - 0
panda/src/char/characterSlider.cxx

@@ -44,6 +44,14 @@ CharacterSlider(PartGroup *parent, const std::string &name)
   : MovingPartScalar(parent, name) {
 }
 
+/**
+ *
+ */
+CharacterSlider::
+CharacterSlider(PartGroup *parent, const std::string &name, const PN_stdfloat &default_value)
+  : MovingPartScalar(parent, name, default_value) {
+}
+
 /**
  *
  */

+ 1 - 0
panda/src/char/characterSlider.h

@@ -32,6 +32,7 @@ protected:
 
 PUBLISHED:
   explicit CharacterSlider(PartGroup *parent, const std::string &name);
+  explicit CharacterSlider(PartGroup *parent, const std::string &name, const PN_stdfloat &default_value);
   virtual ~CharacterSlider();
 
 public:

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

@@ -74,8 +74,11 @@ protected:
   virtual void mouse_mode_relative();
 
 private:
+  NSData *load_image_data(const Filename &filename);
   NSImage *load_image(const Filename &filename);
 
+  NSCursor *load_cursor(const Filename &filename);
+
   void handle_modifier(NSUInteger modifierFlags, NSUInteger mask, ButtonHandle button);
   ButtonHandle map_key(unsigned short c) const;
   ButtonHandle map_raw_key(unsigned short keycode) const;
@@ -94,7 +97,7 @@ private:
   CGDisplayModeRef _fullscreen_mode;
   CGDisplayModeRef _windowed_mode;
 
-  typedef pmap<Filename, NSImage*> IconImages;
+  typedef pmap<Filename, NSData*> IconImages;
   IconImages _images;
 
 public:

+ 83 - 27
panda/src/cocoadisplay/cocoaGraphicsWindow.mm

@@ -568,17 +568,17 @@ open_window() {
   }
 
   if (_properties.has_cursor_filename()) {
-    NSImage *image = load_image(_properties.get_cursor_filename());
-    NSCursor *cursor = nil;
-    // TODO: allow setting the hotspot, read it from file when loading .cur.
-    if (image != nil) {
-      cursor = [[NSCursor alloc] initWithImage:image hotSpot:NSMakePoint(0, 0)];
-    }
+    NSCursor *cursor = load_cursor(_properties.get_cursor_filename());
+
     if (cursor != nil) {
+      if (_cursor != nil) {
+        [_cursor release];
+      }
       _cursor = cursor;
     } else {
       _properties.clear_cursor_filename();
     }
+
     // This will ensure that NSView's resetCursorRects gets called, which sets
     // the appropriate cursor rects.
     [[_view window] invalidateCursorRectsForView:_view];
@@ -1031,19 +1031,15 @@ set_properties_now(WindowProperties &properties) {
       properties.set_cursor_filename(cursor_filename);
       properties.clear_cursor_filename();
     } else {
-      NSImage *image = load_image(cursor_filename);
-      if (image != nil) {
-        NSCursor *cursor;
-        cursor = [[NSCursor alloc] initWithImage:image hotSpot:NSMakePoint(0, 0)];
-        if (cursor != nil) {
-          // Replace the existing cursor.
-          if (_cursor != nil) {
-            [_cursor release];
-          }
-          _cursor = cursor;
-          _properties.set_cursor_filename(cursor_filename);
-          properties.clear_cursor_filename();
+      NSCursor *cursor = load_cursor(cursor_filename);
+      if (cursor != nil) {
+        // Replace the existing cursor.
+        if (_cursor != nil) {
+          [_cursor release];
         }
+        _cursor = cursor;
+        _properties.set_cursor_filename(cursor_filename);
+        properties.clear_cursor_filename();
       }
     }
     // This will ensure that NSView's resetCursorRects gets called, which sets
@@ -1235,11 +1231,12 @@ do_switch_fullscreen(CGDisplayModeRef mode) {
 }
 
 /**
- * Loads the indicated filename and returns an NSImage pointer, or NULL on
- * failure.  Must be called from the window thread.
+ * Loads the indicated filename and returns an NSData pointer (which can then
+ * be used to create a CGImageSource or NSImage), or NULL on failure.  Must be
+ * called from the window thread. May return nil.
  */
-NSImage *CocoaGraphicsWindow::
-load_image(const Filename &filename) {
+NSData *CocoaGraphicsWindow::
+load_image_data(const Filename &filename) {
   if (filename.empty()) {
     return nil;
   }
@@ -1259,7 +1256,6 @@ load_image(const Filename &filename) {
   }
 
   // Look in our index.
-  NSImage *image = nil;
   IconImages::const_iterator it = _images.find(resolved);
   if (it != _images.end()) {
     // Found it.
@@ -1287,21 +1283,81 @@ load_image(const Filename &filename) {
 
   NSData *data = [NSData dataWithBytesNoCopy:buffer length:size];
   if (data == nil) {
+    cocoadisplay_cat.error()
+      << "Could not load image data from file " << filename << "\n";
     return nil;
   }
 
-  image = [[NSImage alloc] initWithData:data];
-  [data release];
+  _images[resolved] = data;
+  return data;
+}
+
+/**
+ * Wraps image data loaded by load_image_data with an NSImage. The returned
+ * pointer is autoreleased. May return nil.
+ */
+NSImage *CocoaGraphicsWindow::
+load_image(const Filename &filename) {
+  NSData *image_data = load_image_data(filename);
+  NSImage *image = [[[NSImage alloc] initWithData:image_data] autorelease];
   if (image == nil) {
     cocoadisplay_cat.error()
       << "Could not load image from file " << filename << "\n";
     return nil;
   }
-
-  _images[resolved] = image;
   return image;
 }
 
+/**
+ * Returns a cursor with the proper hotspot if a .cur filename is passed in.
+ * You must release the returned pointer. May return nil.
+ */
+NSCursor *CocoaGraphicsWindow::
+load_cursor(const Filename &filename) {
+  NSData *image_data = load_image_data(cursor_filename);
+  if (image_data == nil) {
+    return nil;
+  }
+
+  // Read the metadata from the image, which should contain hotspotX and
+  // hotspotY properties.
+  CGImageSourceRef cg_image = CGImageSourceCreateWithData((CFDataRef)image_data, nullptr);
+  if (cg_image == NULL) {
+    return nil;
+  }
+
+  NSDictionary *image_props = (NSDictionary *)CGImageSourceCopyPropertiesAtIndex(cg_image, 0, nil);
+  CFRelease(cg_image);
+
+  if (image_props == nil) {
+    return nil;
+  }
+
+  CGFloat hotspot_x = 0.0f;
+  CGFloat hotspot_y = 0.0f;
+  if (image_props[@"hotspotX"] != nil) {
+    hotspot_x = [(NSNumber *)image_props[@"hotspotX"] floatValue];
+  }
+  if (image_props[@"hotspotY"] != nil) {
+    hotspot_y = [(NSNumber *)image_props[@"hotspotY"] floatValue];
+  }
+  [image_props release];
+
+  NSImage *image = [[NSImage alloc] initWithData:image_data];
+
+  NSCursor *cursor;
+  if (image != nil) {
+    // Apple recognizes that hotspots are usually specified from a .cur
+    // file, whose origin is in the top-left, so there's no need to flip
+    // it like most other Cocoa coordinates.
+    cursor = [[NSCursor alloc] initWithImage:image
+                               hotSpot:NSMakePoint(hotspot_x, hotspot_y)];
+    [image release];
+  }
+
+  return cursor;
+}
+
 /**
  * Called by CocoaPandaView or the window delegate when the frame rect
  * changes.

+ 1 - 1
panda/src/device/inputDeviceNode.cxx

@@ -54,7 +54,7 @@ void InputDeviceNode::
 do_transmit_data(DataGraphTraverser *, const DataNodeTransmit &,
                  DataNodeTransmit &output) {
 
-  if (_device == nullptr && !_device->is_connected()) {
+  if (_device == nullptr || !_device->is_connected()) {
     return;
   }
 

+ 0 - 35
panda/src/device/ioKitInputDevice.cxx

@@ -23,15 +23,6 @@
 #include "gamepadButton.h"
 #include "mouseButton.h"
 
-static void removal_callback(void *ctx, IOReturn result, void *sender) {
-  // 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->test_ref_count_integrity());
-  input_device->on_remove();
-}
-
 /**
  * Protected constructor.
  */
@@ -139,7 +130,6 @@ IOKitInputDevice(IOHIDDeviceRef device) :
   }
 
   _is_connected = true;
-  IOHIDDeviceRegisterRemovalCallback(device, removal_callback, this);
 }
 
 /**
@@ -149,31 +139,6 @@ IOKitInputDevice::
 ~IOKitInputDevice() {
 }
 
-/**
- * The nonstatic version of on_remove_device.
- */
-void IOKitInputDevice::
-on_remove() {
-  {
-    LightMutexHolder holder(_lock);
-    if (!_is_connected) {
-      return;
-    }
-    _is_connected = false;
-  }
-
-  if (device_cat.is_debug()) {
-    device_cat.debug()
-      << "Removed input device " << *this << "\n";
-  }
-
-  IOHIDDeviceClose(_device, kIOHIDOptionsTypeNone);
-
-  InputDeviceManager *mgr = InputDeviceManager::get_global_ptr();
-  nassertv(mgr != nullptr);
-  mgr->remove_device(this);
-}
-
 /**
  *
  */

+ 0 - 2
panda/src/device/ioKitInputDevice.h

@@ -30,8 +30,6 @@ public:
   IOKitInputDevice(IOHIDDeviceRef device);
   ~IOKitInputDevice();
 
-  void on_remove();
-
 private:
   void parse_element(IOHIDElementRef element);
 

+ 30 - 2
panda/src/device/ioKitInputDeviceManager.cxx

@@ -59,6 +59,7 @@ IOKitInputDeviceManager() {
   CFRelease(match);
 
   IOHIDManagerRegisterDeviceMatchingCallback(_hid_manager, on_match_device, this);
+  IOHIDManagerRegisterDeviceRemovalCallback(_hid_manager, on_remove_device, this);
   IOHIDManagerScheduleWithRunLoop(_hid_manager, CFRunLoopGetMain(), kCFRunLoopCommonModes);
   IOHIDManagerOpen(_hid_manager, kIOHIDOptionsTypeNone);
 }
@@ -78,16 +79,43 @@ IOKitInputDeviceManager::
  */
 void IOKitInputDeviceManager::
 on_match_device(void *ctx, IOReturn result, void *sender, IOHIDDeviceRef device) {
-  InputDeviceManager *mgr = (InputDeviceManager *)ctx;
+  IOKitInputDeviceManager *mgr = (IOKitInputDeviceManager *)ctx;
   nassertv(mgr != nullptr);
   nassertv(device);
 
-  PT(InputDevice) input_device = new IOKitInputDevice(device);
+  IOKitInputDevice *input_device = new IOKitInputDevice(device);
   if (device_cat.is_debug()) {
     device_cat.debug()
       << "Discovered input device " << *input_device << "\n";
   }
   mgr->add_device(input_device);
+  mgr->_devices_by_hidref[device] = input_device;
 }
 
+/**
+ * Called by IOKit when an input device has disappeared.
+ */
+void IOKitInputDeviceManager::
+on_remove_device(void *ctx, IOReturn result, void *sender, IOHIDDeviceRef device) {
+  IOKitInputDeviceManager *mgr = (IOKitInputDeviceManager *)ctx;
+  nassertv(mgr != nullptr);
+  nassertv(device);
+
+  auto it = mgr->_devices_by_hidref.find(device);
+  nassertv(it != mgr->_devices_by_hidref.end());
+  IOKitInputDevice *input_device = it->second;
+
+  input_device->set_connected(false);
+
+  mgr->_devices_by_hidref.erase(device);
+
+  IOHIDDeviceClose(device, kIOHIDOptionsTypeNone);
+
+  if (device_cat.is_debug()) {
+    device_cat.debug()
+      << "Removed input device " << *input_device << "\n";
+  }
+
+  mgr->remove_device(input_device);
+}
 #endif

+ 11 - 0
panda/src/device/ioKitInputDeviceManager.h

@@ -19,6 +19,8 @@
 #if defined(__APPLE__) && !defined(CPPPARSER)
 #include <IOKit/hid/IOHIDManager.h>
 
+class IOKitInputDevice;
+
 /**
  * The macOS implementation of InputDeviceManager.
  */
@@ -30,7 +32,16 @@ protected:
 protected:
   IOHIDManagerRef _hid_manager;
 
+  // The device removal callback method we need to use requires us to remember
+  // which IOKitInputDevice corresponds to which IOHIDDeviceRef. This is the
+  // same strategy used by winInputDevice and friends.
+  //
+  // We can make this a mapping to raw pointers since we know _devices will be
+  // holding a reference until remove_device is called.
+  pmap<IOHIDDeviceRef, IOKitInputDevice *> _devices_by_hidref;
+
   static void on_match_device(void *ctx, IOReturn result, void *sender, IOHIDDeviceRef device);
+  static void on_remove_device(void *ctx, IOReturn result, void *sender, IOHIDDeviceRef device);
 
   friend class InputDeviceManager;
 };

+ 0 - 9
panda/src/vrpn/vrpnAnalog.I

@@ -28,12 +28,3 @@ INLINE bool VrpnAnalog::
 is_empty() const {
   return _devices.empty();
 }
-
-/**
- * Polls the connected device.  Normally you should not call this directly;
- * this will be called by the VrpnClient.
- */
-INLINE void VrpnAnalog::
-poll() {
-  _analog->mainloop();
-}

+ 9 - 0
panda/src/vrpn/vrpnAnalog.cxx

@@ -70,6 +70,15 @@ unmark(VrpnAnalogDevice *device) {
   }
 }
 
+/**
+ * Polls the connected device.  Normally you should not call this directly;
+ * this will be called by the VrpnClient.
+ */
+void VrpnAnalog::
+poll() {
+  _analog->mainloop();
+}
+
 /**
  *
  */

+ 1 - 1
panda/src/vrpn/vrpnAnalog.h

@@ -46,7 +46,7 @@ public:
   void mark(VrpnAnalogDevice *device);
   void unmark(VrpnAnalogDevice *device);
 
-  INLINE void poll();
+  void poll();
 
   void output(std::ostream &out) const;
   void write(std::ostream &out, int indent_level = 0) const;

+ 0 - 9
panda/src/vrpn/vrpnButton.I

@@ -28,12 +28,3 @@ INLINE bool VrpnButton::
 is_empty() const {
   return _devices.empty();
 }
-
-/**
- * Polls the connected device.  Normally you should not call this directly;
- * this will be called by the VrpnClient.
- */
-INLINE void VrpnButton::
-poll() {
-  _button->mainloop();
-}

+ 9 - 0
panda/src/vrpn/vrpnButton.cxx

@@ -70,6 +70,15 @@ unmark(VrpnButtonDevice *device) {
   }
 }
 
+/**
+ * Polls the connected device.  Normally you should not call this directly;
+ * this will be called by the VrpnClient.
+ */
+void VrpnButton::
+poll() {
+  _button->mainloop();
+}
+
 /**
  *
  */

+ 1 - 1
panda/src/vrpn/vrpnButton.h

@@ -45,7 +45,7 @@ public:
   void mark(VrpnButtonDevice *device);
   void unmark(VrpnButtonDevice *device);
 
-  INLINE void poll();
+  void poll();
 
   void output(std::ostream &out) const;
   void write(std::ostream &out, int indent_level = 0) const;

+ 0 - 19
panda/src/vrpn/vrpnClient.I

@@ -19,25 +19,6 @@ get_server_name() const {
   return _server_name;
 }
 
-/**
- * Returns true if everything seems to be kosher with the server (even if
- * there is no connection), or false otherwise.
- */
-INLINE bool VrpnClient::
-is_valid() const {
-  return (_connection->doing_okay() != 0);
-}
-
-/**
- * Returns true if the connection is established successfully, false
- * otherwise.
- */
-INLINE bool VrpnClient::
-is_connected() const {
-  return (_connection->connected() != 0);
-}
-
-
 /**
  * Little inline function to convert a struct timeval to only seconds
  */

+ 18 - 0
panda/src/vrpn/vrpnClient.cxx

@@ -60,6 +60,24 @@ VrpnClient::
   delete _connection;
 }
 
+/**
+ * Returns true if everything seems to be kosher with the server (even if
+ * there is no connection), or false otherwise.
+ */
+bool VrpnClient::
+is_valid() const {
+  return (_connection->doing_okay() != 0);
+}
+
+/**
+ * Returns true if the connection is established successfully, false
+ * otherwise.
+ */
+bool VrpnClient::
+is_connected() const {
+  return (_connection->connected() != 0);
+}
+
 /**
  * Writes a list of the active devices that the VrpnClient is currently
  * polling each frame.

+ 2 - 2
panda/src/vrpn/vrpnClient.h

@@ -38,8 +38,8 @@ PUBLISHED:
   ~VrpnClient();
 
   INLINE const std::string &get_server_name() const;
-  INLINE bool is_valid() const;
-  INLINE bool is_connected() const;
+  bool is_valid() const;
+  bool is_connected() const;
 
   void write(std::ostream &out, int indent_level = 0) const;
 

+ 0 - 9
panda/src/vrpn/vrpnDial.I

@@ -27,12 +27,3 @@ INLINE bool VrpnDial::
 is_empty() const {
   return _devices.empty();
 }
-
-/**
- * Polls the connected device.  Normally you should not call this directly;
- * this will be called by the VrpnClient.
- */
-INLINE void VrpnDial::
-poll() {
-  _dial->mainloop();
-}

+ 9 - 0
panda/src/vrpn/vrpnDial.cxx

@@ -70,6 +70,15 @@ unmark(VrpnDialDevice *device) {
   }
 }
 
+/**
+ * Polls the connected device.  Normally you should not call this directly;
+ * this will be called by the VrpnClient.
+ */
+void VrpnDial::
+poll() {
+  _dial->mainloop();
+}
+
 /**
  *
  */

+ 1 - 1
panda/src/vrpn/vrpnDial.h

@@ -45,7 +45,7 @@ public:
   void mark(VrpnDialDevice *device);
   void unmark(VrpnDialDevice *device);
 
-  INLINE void poll();
+  void poll();
 
   void output(std::ostream &out) const;
   void write(std::ostream &out, int indent_level = 0) const;

+ 0 - 9
panda/src/vrpn/vrpnTracker.I

@@ -28,12 +28,3 @@ INLINE bool VrpnTracker::
 is_empty() const {
   return _devices.empty();
 }
-
-/**
- * Polls the connected device.  Normally you should not call this directly;
- * this will be called by the VrpnClient.
- */
-INLINE void VrpnTracker::
-poll() {
-  _tracker->mainloop();
-}

+ 9 - 0
panda/src/vrpn/vrpnTracker.cxx

@@ -72,6 +72,15 @@ unmark(VrpnTrackerDevice *device) {
   }
 }
 
+/**
+ * Polls the connected device.  Normally you should not call this directly;
+ * this will be called by the VrpnClient.
+ */
+void VrpnTracker::
+poll() {
+  _tracker->mainloop();
+}
+
 /**
  *
  */

+ 1 - 1
panda/src/vrpn/vrpnTracker.h

@@ -45,7 +45,7 @@ public:
   void mark(VrpnTrackerDevice *device);
   void unmark(VrpnTrackerDevice *device);
 
-  INLINE void poll();
+  void poll();
 
   void output(std::ostream &out) const;
   void write(std::ostream &out, int indent_level = 0) const;