diff --git a/direct/src/actor/Actor.py b/direct/src/actor/Actor.py index d3e55c980a..4fbd7be253 100644 --- a/direct/src/actor/Actor.py +++ b/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() diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index cc8304cfa4..a0ff853cc1 100755 --- a/makepanda/makepanda.py +++ b/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/ diff --git a/panda/src/chan/animChannelScalarTable.h b/panda/src/chan/animChannelScalarTable.h index 10e54815e3..db92167f56 100644 --- a/panda/src/chan/animChannelScalarTable.h +++ b/panda/src/chan/animChannelScalarTable.h @@ -30,9 +30,10 @@ protected: AnimChannelScalarTable(); AnimChannelScalarTable(AnimGroup *parent, const AnimChannelScalarTable ©); -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); diff --git a/panda/src/char/characterSlider.cxx b/panda/src/char/characterSlider.cxx index b4ced5e782..8885c3acfd 100644 --- a/panda/src/char/characterSlider.cxx +++ b/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) { +} + /** * */ diff --git a/panda/src/char/characterSlider.h b/panda/src/char/characterSlider.h index 9871095afd..54cc7cfd1d 100644 --- a/panda/src/char/characterSlider.h +++ b/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: diff --git a/panda/src/cocoadisplay/cocoaGraphicsWindow.h b/panda/src/cocoadisplay/cocoaGraphicsWindow.h index 5fb49093d3..1381339630 100644 --- a/panda/src/cocoadisplay/cocoaGraphicsWindow.h +++ b/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 IconImages; + typedef pmap IconImages; IconImages _images; public: diff --git a/panda/src/cocoadisplay/cocoaGraphicsWindow.mm b/panda/src/cocoadisplay/cocoaGraphicsWindow.mm index 7d19c6bb9e..bfbb6edf25 100644 --- a/panda/src/cocoadisplay/cocoaGraphicsWindow.mm +++ b/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. diff --git a/panda/src/device/inputDeviceNode.cxx b/panda/src/device/inputDeviceNode.cxx index bdf849b93b..b5e749b2d6 100644 --- a/panda/src/device/inputDeviceNode.cxx +++ b/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; } diff --git a/panda/src/device/ioKitInputDevice.cxx b/panda/src/device/ioKitInputDevice.cxx index 079614a99f..e80dfccd94 100644 --- a/panda/src/device/ioKitInputDevice.cxx +++ b/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); -} - /** * */ diff --git a/panda/src/device/ioKitInputDevice.h b/panda/src/device/ioKitInputDevice.h index f32f9c4256..e355a95826 100644 --- a/panda/src/device/ioKitInputDevice.h +++ b/panda/src/device/ioKitInputDevice.h @@ -30,8 +30,6 @@ public: IOKitInputDevice(IOHIDDeviceRef device); ~IOKitInputDevice(); - void on_remove(); - private: void parse_element(IOHIDElementRef element); diff --git a/panda/src/device/ioKitInputDeviceManager.cxx b/panda/src/device/ioKitInputDeviceManager.cxx index 6874d9baba..3f1c807469 100644 --- a/panda/src/device/ioKitInputDeviceManager.cxx +++ b/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 diff --git a/panda/src/device/ioKitInputDeviceManager.h b/panda/src/device/ioKitInputDeviceManager.h index f028a5f153..80005f99ab 100644 --- a/panda/src/device/ioKitInputDeviceManager.h +++ b/panda/src/device/ioKitInputDeviceManager.h @@ -19,6 +19,8 @@ #if defined(__APPLE__) && !defined(CPPPARSER) #include +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 _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; }; diff --git a/panda/src/vrpn/vrpnAnalog.I b/panda/src/vrpn/vrpnAnalog.I index afd951e417..234e6b4616 100644 --- a/panda/src/vrpn/vrpnAnalog.I +++ b/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(); -} diff --git a/panda/src/vrpn/vrpnAnalog.cxx b/panda/src/vrpn/vrpnAnalog.cxx index 2745aab75e..b0b81c8867 100644 --- a/panda/src/vrpn/vrpnAnalog.cxx +++ b/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(); +} + /** * */ diff --git a/panda/src/vrpn/vrpnAnalog.h b/panda/src/vrpn/vrpnAnalog.h index 07f3c84b21..34bc51f277 100644 --- a/panda/src/vrpn/vrpnAnalog.h +++ b/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; diff --git a/panda/src/vrpn/vrpnButton.I b/panda/src/vrpn/vrpnButton.I index ed10b89fae..e083d0325b 100644 --- a/panda/src/vrpn/vrpnButton.I +++ b/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(); -} diff --git a/panda/src/vrpn/vrpnButton.cxx b/panda/src/vrpn/vrpnButton.cxx index 3312afa31d..eefbcf1015 100644 --- a/panda/src/vrpn/vrpnButton.cxx +++ b/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(); +} + /** * */ diff --git a/panda/src/vrpn/vrpnButton.h b/panda/src/vrpn/vrpnButton.h index 1b9fc14cf4..3b151b5130 100644 --- a/panda/src/vrpn/vrpnButton.h +++ b/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; diff --git a/panda/src/vrpn/vrpnClient.I b/panda/src/vrpn/vrpnClient.I index 486a4e29f6..908c77a0c9 100644 --- a/panda/src/vrpn/vrpnClient.I +++ b/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 */ diff --git a/panda/src/vrpn/vrpnClient.cxx b/panda/src/vrpn/vrpnClient.cxx index 1fb7fcb4ff..9bf4ea2795 100644 --- a/panda/src/vrpn/vrpnClient.cxx +++ b/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. diff --git a/panda/src/vrpn/vrpnClient.h b/panda/src/vrpn/vrpnClient.h index 127492d9cf..979351eac9 100644 --- a/panda/src/vrpn/vrpnClient.h +++ b/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; diff --git a/panda/src/vrpn/vrpnDial.I b/panda/src/vrpn/vrpnDial.I index 2cb9981826..3813aa02be 100644 --- a/panda/src/vrpn/vrpnDial.I +++ b/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(); -} diff --git a/panda/src/vrpn/vrpnDial.cxx b/panda/src/vrpn/vrpnDial.cxx index 62b1fe73df..04f19762df 100644 --- a/panda/src/vrpn/vrpnDial.cxx +++ b/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(); +} + /** * */ diff --git a/panda/src/vrpn/vrpnDial.h b/panda/src/vrpn/vrpnDial.h index e98fd7d99d..e7be9c8aa1 100644 --- a/panda/src/vrpn/vrpnDial.h +++ b/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; diff --git a/panda/src/vrpn/vrpnTracker.I b/panda/src/vrpn/vrpnTracker.I index b651121923..89c91084b5 100644 --- a/panda/src/vrpn/vrpnTracker.I +++ b/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(); -} diff --git a/panda/src/vrpn/vrpnTracker.cxx b/panda/src/vrpn/vrpnTracker.cxx index e1b08e2a72..046eaf1d69 100644 --- a/panda/src/vrpn/vrpnTracker.cxx +++ b/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(); +} + /** * */ diff --git a/panda/src/vrpn/vrpnTracker.h b/panda/src/vrpn/vrpnTracker.h index 1b6c30347d..bb6651cff7 100644 --- a/panda/src/vrpn/vrpnTracker.h +++ b/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;