ControllerInterface: enable hotplugging on macOS
This commit is contained in:
parent
a3d2dead76
commit
7ed8fb95c5
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
Loading…
Reference in New Issue