ControllerInterface: enable hotplugging on macOS

This commit is contained in:
Michael Maltese 2016-10-17 23:00:11 -07:00
parent a3d2dead76
commit 7ed8fb95c5
6 changed files with 117 additions and 16 deletions

View File

@ -2,23 +2,29 @@
// Licensed under GPLv2+ // Licensed under GPLv2+
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include <thread>
#include <Cocoa/Cocoa.h> #include <Cocoa/Cocoa.h>
#include <Foundation/Foundation.h> #include <Foundation/Foundation.h>
#include <IOKit/hid/IOHIDLib.h> #include <IOKit/hid/IOHIDLib.h>
#include "Common/Logging/Log.h"
#include "Common/StringUtil.h" #include "Common/StringUtil.h"
#include "Common/Thread.h"
#include "InputCommon/ControllerInterface/ControllerInterface.h" #include "InputCommon/ControllerInterface/ControllerInterface.h"
#include "InputCommon/ControllerInterface/OSX/OSX.h" #include "InputCommon/ControllerInterface/OSX/OSX.h"
#include "InputCommon/ControllerInterface/OSX/OSXJoystick.h" #include "InputCommon/ControllerInterface/OSX/OSXJoystick.h"
#include "InputCommon/ControllerInterface/OSX/OSXKeyboard.h" #include "InputCommon/ControllerInterface/OSX/OSXKeyboard.h"
#include "InputCommon/ControllerInterface/OSX/RunLoopStopper.h"
#include <map>
namespace ciface namespace ciface
{ {
namespace OSX namespace OSX
{ {
constexpr CFTimeInterval FOREVER = 1e20;
static std::thread s_hotplug_thread;
static RunLoopStopper s_stopper;
static IOHIDManagerRef HIDManager = nullptr; static IOHIDManagerRef HIDManager = nullptr;
static CFStringRef OurRunLoop = CFSTR("DolphinOSXInput"); static CFStringRef OurRunLoop = CFSTR("DolphinOSXInput");
@ -134,13 +140,36 @@ static void DeviceDebugPrint(IOHIDDeviceRef device)
static void* g_window; static void* g_window;
static void DeviceMatching_callback(void* inContext, IOReturn inResult, void* inSender, static std::string GetDeviceRefName(IOHIDDeviceRef inIOHIDDeviceRef)
{
const NSString* name = reinterpret_cast<const NSString*>(
IOHIDDeviceGetProperty(inIOHIDDeviceRef, CFSTR(kIOHIDProductKey)));
return (name != nullptr) ? StripSpaces([name UTF8String]) : "Unknown device";
}
static void DeviceRemovalCallback(void* inContext, IOReturn inResult, void* inSender,
IOHIDDeviceRef inIOHIDDeviceRef) IOHIDDeviceRef inIOHIDDeviceRef)
{ {
NSString* pName = (NSString*)IOHIDDeviceGetProperty(inIOHIDDeviceRef, CFSTR(kIOHIDProductKey)); g_controller_interface.RemoveDevice([&inIOHIDDeviceRef](const auto* device) {
std::string name = (pName != nullptr) ? StripSpaces([pName UTF8String]) : "Unknown device"; const Joystick* joystick = dynamic_cast<const Joystick*>(device);
if (joystick && joystick->IsSameDevice(inIOHIDDeviceRef))
return true;
const Keyboard* keyboard = dynamic_cast<const Keyboard*>(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); DeviceDebugPrint(inIOHIDDeviceRef);
std::string name = GetDeviceRefName(inIOHIDDeviceRef);
// Add a device if it's of a type we want // Add a device if it's of a type we want
if (IOHIDDeviceConformsTo(inIOHIDDeviceRef, kHIDPage_GenericDesktop, kHIDUsage_GD_Keyboard)) if (IOHIDDeviceConformsTo(inIOHIDDeviceRef, kHIDPage_GenericDesktop, kHIDUsage_GD_Keyboard))
@ -151,39 +180,54 @@ static void DeviceMatching_callback(void* inContext, IOReturn inResult, void* in
#endif #endif
else else
g_controller_interface.AddDevice(std::make_shared<Joystick>(inIOHIDDeviceRef, name)); g_controller_interface.AddDevice(std::make_shared<Joystick>(inIOHIDDeviceRef, name));
NOTICE_LOG(SERIALINTERFACE, "Added device: %s", name.c_str());
g_controller_interface.InvokeHotplugCallbacks();
} }
void Init(void* window) void Init(void* window)
{ {
HIDManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); HIDManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
if (!HIDManager) if (!HIDManager)
NSLog(@"Failed to create HID Manager reference"); ERROR_LOG(SERIALINTERFACE, "Failed to create HID Manager reference");
g_window = window; g_window = window;
IOHIDManagerSetDeviceMatching(HIDManager, nullptr); 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 // 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 // Match devices that are plugged in right now
IOHIDManagerScheduleWithRunLoop(HIDManager, CFRunLoopGetCurrent(), OurRunLoop); 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) while (CFRunLoopRunInMode(OurRunLoop, 0, TRUE) == kCFRunLoopRunHandledSource)
{ {
}; };
// Things should be configured now
// Disable hotplugging and other scheduling
IOHIDManagerRegisterDeviceMatchingCallback(HIDManager, nullptr, nullptr);
IOHIDManagerUnscheduleFromRunLoop(HIDManager, CFRunLoopGetCurrent(), OurRunLoop); 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() void DeInit()
{ {
s_stopper.Signal();
s_hotplug_thread.join();
// This closes all devices as well // This closes all devices as well
IOHIDManagerClose(HIDManager, kIOHIDOptionsTypeNone); IOHIDManagerClose(HIDManager, kIOHIDOptionsTypeNone);
CFRelease(HIDManager); CFRelease(HIDManager);

View File

@ -78,6 +78,7 @@ public:
std::string GetName() const override; std::string GetName() const override;
std::string GetSource() const override; std::string GetSource() const override;
bool IsSameDevice(const IOHIDDeviceRef) const;
private: private:
const IOHIDDeviceRef m_device; const IOHIDDeviceRef m_device;

View File

@ -276,5 +276,10 @@ std::string Joystick::Hat::GetName() const
{ {
return m_name; return m_name;
} }
bool Joystick::IsSameDevice(const IOHIDDeviceRef other_device) const
{
return m_device == other_device;
}
} }
} }

View File

@ -64,6 +64,7 @@ public:
std::string GetName() const override; std::string GetName() const override;
std::string GetSource() const override; std::string GetSource() const override;
bool IsSameDevice(const IOHIDDeviceRef) const;
private: private:
struct struct

View File

@ -263,5 +263,10 @@ std::string Keyboard::Key::GetName() const
{ {
return m_name; return m_name;
} }
bool Keyboard::IsSameDevice(const IOHIDDeviceRef other_device) const
{
return m_device == other_device;
}
} }
} }

View File

@ -0,0 +1,45 @@
// Copyright 2016 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#include <Foundation/Foundation.h>
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