diff --git a/Source/Core/InputCommon/ControllerInterface/OSX/OSX.mm b/Source/Core/InputCommon/ControllerInterface/OSX/OSX.mm index e575260a89..c69d2c3af2 100644 --- a/Source/Core/InputCommon/ControllerInterface/OSX/OSX.mm +++ b/Source/Core/InputCommon/ControllerInterface/OSX/OSX.mm @@ -2,23 +2,29 @@ // Licensed under GPLv2+ // Refer to the license.txt file included. +#include + #include #include #include +#include "Common/Logging/Log.h" #include "Common/StringUtil.h" +#include "Common/Thread.h" #include "InputCommon/ControllerInterface/ControllerInterface.h" #include "InputCommon/ControllerInterface/OSX/OSX.h" #include "InputCommon/ControllerInterface/OSX/OSXJoystick.h" #include "InputCommon/ControllerInterface/OSX/OSXKeyboard.h" - -#include +#include "InputCommon/ControllerInterface/OSX/RunLoopStopper.h" namespace ciface { namespace OSX { +constexpr CFTimeInterval FOREVER = 1e20; +static std::thread s_hotplug_thread; +static RunLoopStopper s_stopper; static IOHIDManagerRef HIDManager = nullptr; static CFStringRef OurRunLoop = CFSTR("DolphinOSXInput"); @@ -134,13 +140,36 @@ static void DeviceDebugPrint(IOHIDDeviceRef device) static void* g_window; -static void DeviceMatching_callback(void* inContext, IOReturn inResult, void* inSender, - IOHIDDeviceRef inIOHIDDeviceRef) +static std::string GetDeviceRefName(IOHIDDeviceRef inIOHIDDeviceRef) { - NSString* pName = (NSString*)IOHIDDeviceGetProperty(inIOHIDDeviceRef, CFSTR(kIOHIDProductKey)); - std::string name = (pName != nullptr) ? StripSpaces([pName UTF8String]) : "Unknown device"; + const NSString* name = reinterpret_cast( + IOHIDDeviceGetProperty(inIOHIDDeviceRef, CFSTR(kIOHIDProductKey))); + return (name != nullptr) ? StripSpaces([name UTF8String]) : "Unknown device"; +} +static void DeviceRemovalCallback(void* inContext, IOReturn inResult, void* inSender, + IOHIDDeviceRef inIOHIDDeviceRef) +{ + g_controller_interface.RemoveDevice([&inIOHIDDeviceRef](const auto* device) { + const Joystick* joystick = dynamic_cast(device); + if (joystick && joystick->IsSameDevice(inIOHIDDeviceRef)) + return true; + + const Keyboard* keyboard = dynamic_cast(device); + if (keyboard && keyboard->IsSameDevice(inIOHIDDeviceRef)) + return true; + + return false; + }); + g_controller_interface.InvokeHotplugCallbacks(); + NOTICE_LOG(SERIALINTERFACE, "Removed device: %s", GetDeviceRefName(inIOHIDDeviceRef).c_str()); +} + +static void DeviceMatchingCallback(void* inContext, IOReturn inResult, void* inSender, + IOHIDDeviceRef inIOHIDDeviceRef) +{ DeviceDebugPrint(inIOHIDDeviceRef); + std::string name = GetDeviceRefName(inIOHIDDeviceRef); // Add a device if it's of a type we want if (IOHIDDeviceConformsTo(inIOHIDDeviceRef, kHIDPage_GenericDesktop, kHIDUsage_GD_Keyboard)) @@ -151,39 +180,54 @@ static void DeviceMatching_callback(void* inContext, IOReturn inResult, void* in #endif else g_controller_interface.AddDevice(std::make_shared(inIOHIDDeviceRef, name)); + + NOTICE_LOG(SERIALINTERFACE, "Added device: %s", name.c_str()); + g_controller_interface.InvokeHotplugCallbacks(); } void Init(void* window) { HIDManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); if (!HIDManager) - NSLog(@"Failed to create HID Manager reference"); + ERROR_LOG(SERIALINTERFACE, "Failed to create HID Manager reference"); g_window = window; IOHIDManagerSetDeviceMatching(HIDManager, nullptr); + if (IOHIDManagerOpen(HIDManager, kIOHIDOptionsTypeNone) != kIOReturnSuccess) + ERROR_LOG(SERIALINTERFACE, "Failed to open HID Manager"); // Callbacks for acquisition or loss of a matching device - IOHIDManagerRegisterDeviceMatchingCallback(HIDManager, DeviceMatching_callback, nullptr); + IOHIDManagerRegisterDeviceMatchingCallback(HIDManager, DeviceMatchingCallback, nullptr); + IOHIDManagerRegisterDeviceRemovalCallback(HIDManager, DeviceRemovalCallback, nullptr); // Match devices that are plugged in right now IOHIDManagerScheduleWithRunLoop(HIDManager, CFRunLoopGetCurrent(), OurRunLoop); - if (IOHIDManagerOpen(HIDManager, kIOHIDOptionsTypeNone) != kIOReturnSuccess) - NSLog(@"Failed to open HID Manager"); - - // Wait while current devices are initialized while (CFRunLoopRunInMode(OurRunLoop, 0, TRUE) == kCFRunLoopRunHandledSource) { }; - - // Things should be configured now - // Disable hotplugging and other scheduling - IOHIDManagerRegisterDeviceMatchingCallback(HIDManager, nullptr, nullptr); IOHIDManagerUnscheduleFromRunLoop(HIDManager, CFRunLoopGetCurrent(), OurRunLoop); + + // Enable hotplugging + s_hotplug_thread = std::thread([] { + Common::SetCurrentThreadName("IOHIDManager Hotplug Thread"); + NOTICE_LOG(SERIALINTERFACE, "IOHIDManager hotplug thread started"); + + IOHIDManagerScheduleWithRunLoop(HIDManager, CFRunLoopGetCurrent(), OurRunLoop); + s_stopper.AddToRunLoop(CFRunLoopGetCurrent(), OurRunLoop); + CFRunLoopRunInMode(OurRunLoop, FOREVER, FALSE); + s_stopper.RemoveFromRunLoop(CFRunLoopGetCurrent(), OurRunLoop); + IOHIDManagerUnscheduleFromRunLoop(HIDManager, CFRunLoopGetCurrent(), OurRunLoop); + + NOTICE_LOG(SERIALINTERFACE, "IOHIDManager hotplug thread stopped"); + }); } void DeInit() { + s_stopper.Signal(); + s_hotplug_thread.join(); + // This closes all devices as well IOHIDManagerClose(HIDManager, kIOHIDOptionsTypeNone); CFRelease(HIDManager); diff --git a/Source/Core/InputCommon/ControllerInterface/OSX/OSXJoystick.h b/Source/Core/InputCommon/ControllerInterface/OSX/OSXJoystick.h index 41ea7c8365..dbe6a725c1 100644 --- a/Source/Core/InputCommon/ControllerInterface/OSX/OSXJoystick.h +++ b/Source/Core/InputCommon/ControllerInterface/OSX/OSXJoystick.h @@ -78,6 +78,7 @@ public: std::string GetName() const override; std::string GetSource() const override; + bool IsSameDevice(const IOHIDDeviceRef) const; private: const IOHIDDeviceRef m_device; diff --git a/Source/Core/InputCommon/ControllerInterface/OSX/OSXJoystick.mm b/Source/Core/InputCommon/ControllerInterface/OSX/OSXJoystick.mm index 19aa9e23b7..e8595153f2 100644 --- a/Source/Core/InputCommon/ControllerInterface/OSX/OSXJoystick.mm +++ b/Source/Core/InputCommon/ControllerInterface/OSX/OSXJoystick.mm @@ -276,5 +276,10 @@ std::string Joystick::Hat::GetName() const { return m_name; } + +bool Joystick::IsSameDevice(const IOHIDDeviceRef other_device) const +{ + return m_device == other_device; +} } } diff --git a/Source/Core/InputCommon/ControllerInterface/OSX/OSXKeyboard.h b/Source/Core/InputCommon/ControllerInterface/OSX/OSXKeyboard.h index d73243259e..3d2dccfb79 100644 --- a/Source/Core/InputCommon/ControllerInterface/OSX/OSXKeyboard.h +++ b/Source/Core/InputCommon/ControllerInterface/OSX/OSXKeyboard.h @@ -64,6 +64,7 @@ public: std::string GetName() const override; std::string GetSource() const override; + bool IsSameDevice(const IOHIDDeviceRef) const; private: struct diff --git a/Source/Core/InputCommon/ControllerInterface/OSX/OSXKeyboard.mm b/Source/Core/InputCommon/ControllerInterface/OSX/OSXKeyboard.mm index 8fb6ebe80d..a4e4bd9312 100644 --- a/Source/Core/InputCommon/ControllerInterface/OSX/OSXKeyboard.mm +++ b/Source/Core/InputCommon/ControllerInterface/OSX/OSXKeyboard.mm @@ -263,5 +263,10 @@ std::string Keyboard::Key::GetName() const { return m_name; } + +bool Keyboard::IsSameDevice(const IOHIDDeviceRef other_device) const +{ + return m_device == other_device; +} } } diff --git a/Source/Core/InputCommon/ControllerInterface/OSX/RunLoopStopper.h b/Source/Core/InputCommon/ControllerInterface/OSX/RunLoopStopper.h new file mode 100644 index 0000000000..298dcd4a80 --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/OSX/RunLoopStopper.h @@ -0,0 +1,45 @@ +// Copyright 2016 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include + +namespace ciface +{ +namespace OSX +{ +class RunLoopStopper +{ + CFRunLoopSourceRef m_source; + CFRunLoopRef m_runloop = nullptr; + +public: + RunLoopStopper() + { + CFRunLoopSourceContext ctx = {.version = 0, + .perform = [](void*) { CFRunLoopStop(CFRunLoopGetCurrent()); }}; + m_source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &ctx); + } + ~RunLoopStopper() { CFRelease(m_source); } + void Signal() + { + CFRunLoopSourceSignal(m_source); + if (m_runloop != nullptr) + CFRunLoopWakeUp(m_runloop); + } + void AddToRunLoop(CFRunLoopRef runloop, CFStringRef mode) + { + m_runloop = runloop; + CFRunLoopAddSource(runloop, m_source, mode); + } + void RemoveFromRunLoop(CFRunLoopRef runloop, CFStringRef mode) + { + m_runloop = nullptr; + CFRunLoopRemoveSource(runloop, m_source, mode); + } +}; + +} // namespace ciface +} // namespace OSX