initial OSX Qt GamePad support

This commit is contained in:
Mahmood - Zer0xFF 2018-10-23 01:40:35 +01:00
parent 5e3a652d48
commit 16e3b06210
12 changed files with 334 additions and 7 deletions

View File

@ -150,6 +150,11 @@ QT5_WRAP_UI(QT_UI_HEADERS ${QT_UIS})
QT5_WRAP_CPP(QT_MOC_SRCS ${QT_MOC_HEADERS})
if(TARGET_PLATFORM_MACOS)
set(QT_SOURCES
${QT_SOURCES}
GamePad/GamePadDeviceListener_OSX.cpp
GamePad/GamePadDeviceListener_OSX.h
)
set(OSX_RES
${CMAKE_CURRENT_SOURCE_DIR}/AppIcon.icns
${CMAKE_CURRENT_SOURCE_DIR}/../../patches.xml
@ -173,6 +178,7 @@ if(TARGET_PLATFORM_MACOS)
MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in"
RESOURCE "${OSX_RES}"
)
list(APPEND PROJECT_LIBS "-framework IOKit -framework CoreFoundation")
elseif(TARGET_PLATFORM_WIN32)
add_executable(Play WIN32 ${QT_SOURCES} ${QT_MOC_SRCS} ${QT_RES_SOURCES} ${QT_UI_HEADERS})
else()

View File

@ -14,7 +14,7 @@
ControllerConfigDialog::ControllerConfigDialog(QWidget* parent)
: QDialog(parent)
, ui(new Ui::ControllerConfigDialog)
#ifdef HAS_LIBEVDEV
#if defined(HAS_LIBEVDEV) || defined(__APPLE__)
, m_inputDeviceManager(std::make_unique<CGamePadDeviceListener>(true))
#endif
{
@ -88,11 +88,11 @@ int ControllerConfigDialog::OpenBindConfigDialog(int index)
InputEventSelectionDialog IESD;
IESD.Setup(button.c_str(), m_inputManager, static_cast<PS2::CControllerInfo::BUTTON>(index));
#ifdef HAS_LIBEVDEV
#if defined(HAS_LIBEVDEV) || defined(__APPLE__)
IESD.SetupInputDeviceManager(m_inputDeviceManager);
#endif
auto res = IESD.exec();
#ifdef HAS_LIBEVDEV
#if defined(HAS_LIBEVDEV) || defined(__APPLE__)
m_inputDeviceManager.get()->DisconnectInputEventCallback();
#endif
return res;

View File

@ -6,6 +6,8 @@
#ifdef HAS_LIBEVDEV
#include "GamePad/GamePadDeviceListener.h"
#elif defined(__APPLE__)
#include "GamePad/GamePadDeviceListener_OSX.h"
#endif
#include "InputBindingManager.h"
@ -32,7 +34,7 @@ private slots:
private:
int OpenBindConfigDialog(int index);
CInputBindingManager* m_inputManager;
#ifdef HAS_LIBEVDEV
#if defined(HAS_LIBEVDEV) || defined(__APPLE__)
std::unique_ptr<CGamePadDeviceListener> m_inputDeviceManager;
#endif
Ui::ControllerConfigDialog* ui;

View File

@ -106,6 +106,80 @@ void InputEventSelectionDialog::SetupInputDeviceManager(std::unique_ptr<CGamePad
GPDL.get()->UpdateOnInputEventCallback(onInput);
}
#elif defined(__APPLE__)
void InputEventSelectionDialog::SetupInputDeviceManager(std::unique_ptr<CGamePadDeviceListener> const& GPDL)
{
auto onInput = [=](std::array<unsigned int, 6> device, int code, int value, IOHIDElementRef elementRef) -> void {
IOHIDElementType type = IOHIDElementGetType(elementRef);
bool is_axis = type == kIOHIDElementTypeInput_Axis || type == kIOHIDElementTypeInput_Misc;
if(!is_axis)
{
if(!setCounter(value)) return;
}
QString key = QString("btn-").append(QString::number(code));
if(is_axis)
{
if(IOHIDElementGetUsage(elementRef) != kHIDUsage_GD_Hatswitch)
{
CFIndex triggerRange = IOHIDElementGetLogicalMax(elementRef) / 100 * 20;
CFIndex triggerVal1 = IOHIDElementGetLogicalMax(elementRef) - triggerRange;
CFIndex triggerVal2 = IOHIDElementGetLogicalMin(elementRef) + triggerRange;
if(value < triggerVal1 && triggerVal2 < value)
{
setCounter(0);
return;
}
setCounter(1);
setSelectedButtonLabelText("Selected Key: " + key);
m_key1.id = code;
m_key1.device = device;
m_key1.type = type;
m_key1.bindtype = CInputBindingManager::BINDINGTYPE::BINDING_SIMPLE;
}
else
{
m_key1.id = code;
m_key1.device = device;
m_key1.type = type;
m_key1.value = value;
m_key1.bindtype = CInputBindingManager::BINDINGTYPE::BINDING_POVHAT;
setSelectedButtonLabelText("Selected Key: " + key);
setCounter(1);
}
}
else
{
if(PS2::CControllerInfo::IsAxis(m_button))
{
if(click_count == 0)
{
setSelectedButtonLabelText("(-) Key Selected: " + key);
m_key1.id = code;
m_key1.device = device;
m_key1.type = type;
m_key1.bindtype = CInputBindingManager::BINDINGTYPE::BINDING_SIMULATEDAXIS;
}
else
{
m_key2.id = code;
m_key2.device = device;
m_key2.type = type;
setSelectedButtonLabelText("(+) Key Selected: " + key);
}
}
else
{
setSelectedButtonLabelText("Selected Key: " + key);
m_key1.id = code;
m_key1.device = device;
m_key1.type = type;
m_key1.bindtype = CInputBindingManager::BINDINGTYPE::BINDING_SIMPLE;
}
}
};
GPDL.get()->UpdateOnInputEventCallback(onInput);
}
#endif
void InputEventSelectionDialog::keyPressEvent(QKeyEvent* ev)

View File

@ -9,6 +9,8 @@
#ifdef HAS_LIBEVDEV
#include "GamePad/GamePadInputEventListener.h"
#include "GamePad/GamePadDeviceListener.h"
#elif defined(__APPLE__)
#include "GamePad/GamePadDeviceListener_OSX.h"
#endif
namespace Ui
@ -25,7 +27,7 @@ public:
~InputEventSelectionDialog();
void Setup(const char* text, CInputBindingManager* inputManager, PS2::CControllerInfo::BUTTON button);
#ifdef HAS_LIBEVDEV
#if defined(HAS_LIBEVDEV) || defined(__APPLE__)
void SetupInputDeviceManager(const std::unique_ptr<CGamePadDeviceListener>& GPDL);
#endif

View File

@ -0,0 +1,132 @@
#include "GamePadDeviceListener_OSX.h"
#include "GamePadUtils.h"
CGamePadDeviceListener::CGamePadDeviceListener(OnInputEvent OnInputEventCallBack, bool filter)
: OnInputEventCallBack(OnInputEventCallBack)
, m_running(true)
, m_filter(filter)
{
m_thread = std::thread([this]() { InputDeviceListenerThread(); });
}
CGamePadDeviceListener::CGamePadDeviceListener(bool filter)
: CGamePadDeviceListener(nullptr, filter)
{
}
CGamePadDeviceListener::~CGamePadDeviceListener()
{
m_running = false;
if(m_thread.joinable())
m_thread.join();
}
void CGamePadDeviceListener::UpdateOnInputEventCallback(CGamePadDeviceListener::OnInputEvent OnInputEventFunction)
{
OnInputEventCallBack = OnInputEventFunction;
}
void CGamePadDeviceListener::DisconnectInputEventCallback()
{
OnInputEventCallBack = nullptr;
}
void CGamePadDeviceListener::UpdateDeviceList()
{
}
CFMutableDictionaryRef CGamePadDeviceListener::CreateDeviceMatchingDictionary(uint32 usagePage, uint32 usage)
{
CFMutableDictionaryRef result = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
if(result == nullptr) throw std::runtime_error("CFDictionaryCreateMutable failed.");
if(usagePage != 0)
{
// Add key for device type to refine the matching dictionary.
CFNumberRef pageCFNumberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usagePage);
if(pageCFNumberRef == nullptr) throw std::runtime_error("CFNumberCreate failed.");
CFDictionarySetValue(result, CFSTR(kIOHIDDeviceUsagePageKey), pageCFNumberRef);
CFRelease(pageCFNumberRef);
// note: the usage is only valid if the usage page is also defined
if(usage != 0)
{
CFNumberRef usageCFNumberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage);
if(usageCFNumberRef == nullptr) throw std::runtime_error("CFNumberCreate failed.");
CFDictionarySetValue(result, CFSTR(kIOHIDDeviceUsageKey), usageCFNumberRef);
CFRelease(usageCFNumberRef);
}
}
return result;
}
void CGamePadDeviceListener::InputValueCallbackStub(void* context, IOReturn result, void* sender, IOHIDValueRef valueRef)
{
auto GPDL = reinterpret_cast<CGamePadDeviceListener*>(context);
if(!GPDL->OnInputEventCallBack)
{
return;
}
IOHIDElementRef elementRef = IOHIDValueGetElement(valueRef);
uint32 usagePage = IOHIDElementGetUsagePage(elementRef);
if(usagePage == kHIDPage_VendorDefinedStart)
{
return;
}
uint32 usage = IOHIDElementGetUsage(elementRef);
CFIndex value = IOHIDValueGetIntegerValue(valueRef);
IOHIDDeviceRef device = IOHIDElementGetDevice(elementRef);
if(GPDL->m_filter)
{
IOHIDElementType type = IOHIDElementGetType(elementRef);
bool is_axis = type == kIOHIDElementTypeInput_Axis || type == kIOHIDElementTypeInput_Misc;
if(is_axis)
{
if(usage != kHIDUsage_GD_Hatswitch)
{
CFIndex maxRange = IOHIDElementGetLogicalMax(elementRef);
CFIndex minRange = IOHIDElementGetLogicalMin(elementRef);
CFIndex neutralRange = (maxRange + minRange) / 2;
CFIndex triggerRange = (neutralRange * 20) / 100;
if((value < minRange || value > maxRange) || (neutralRange + triggerRange > value && value > neutralRange - triggerRange))
{
return;
}
}
}
}
GPDL->OnInputEventCallBack(CGamePadUtils::GetDeviceID(device), usage, value, elementRef);
}
void CGamePadDeviceListener::onDeviceMatched(void* context, IOReturn result, void* sender, IOHIDDeviceRef device)
{
auto GPDL = reinterpret_cast<CGamePadDeviceListener*>(context);
IOHIDDeviceRegisterInputValueCallback(device, GPDL->InputValueCallbackStub, context);
}
void CGamePadDeviceListener::InputDeviceListenerThread()
{
m_hidManager = IOHIDManagerCreate(kCFAllocatorDefault, 0);
{
CFDictionaryRef matchingDict[3];
matchingDict[0] = CreateDeviceMatchingDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick);
matchingDict[1] = CreateDeviceMatchingDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad);
matchingDict[2] = CreateDeviceMatchingDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController);
CFArrayRef array = CFArrayCreate(kCFAllocatorDefault, (const void**)matchingDict, 3, &kCFTypeArrayCallBacks);
CFRelease(matchingDict[0]);
CFRelease(matchingDict[1]);
CFRelease(matchingDict[2]);
IOHIDManagerSetDeviceMatchingMultiple(m_hidManager, array);
}
IOHIDManagerRegisterDeviceMatchingCallback(m_hidManager, onDeviceMatched, this);
//IOHIDManagerRegisterDeviceRemovalCallback(m_hidManager, onDeviceRemoved, this);
IOHIDManagerOpen(m_hidManager, kIOHIDOptionsTypeNone);
IOHIDManagerScheduleWithRunLoop(m_hidManager, CFRunLoopGetCurrent(), CFSTR("CustomLoop"));
while(CFRunLoopRunInMode(CFSTR("CustomLoop"), 0, true) != kCFRunLoopRunStopped && m_running)
{
}
IOHIDManagerClose(m_hidManager, 0);
}

View File

@ -0,0 +1,42 @@
#pragma once
#include <atomic>
#include <array>
#include <boost/signals2.hpp>
#include <thread>
#include <IOKit/hid/IOHIDManager.h>
#include <CoreFoundation/CoreFoundation.h>
#include <boost/filesystem.hpp>
#include "Types.h"
namespace fs = boost::filesystem;
class CGamePadDeviceListener
{
public:
typedef std::function<void(std::array<uint32, 6>, int, int, IOHIDElementRef)> OnInputEvent;
CGamePadDeviceListener(bool f = false);
CGamePadDeviceListener(OnInputEvent, bool f = false);
~CGamePadDeviceListener();
OnInputEvent OnInputEventCallBack;
void UpdateOnInputEventCallback(OnInputEvent);
void DisconnectInputEventCallback();
private:
std::atomic<bool> m_running;
std::thread m_inputdevicelistenerthread;
bool m_filter;
std::thread m_thread;
IOHIDManagerRef m_hidManager;
void UpdateDeviceList();
void AddDevice(const fs::path&);
void InputDeviceListenerThread();
CFMutableDictionaryRef CreateDeviceMatchingDictionary(uint32 usagePage, uint32 usage);
void static InputValueCallbackStub(void* context, IOReturn result, void* sender, IOHIDValueRef valueRef);
void static onDeviceMatched(void* context, IOReturn result, void* sender, IOHIDDeviceRef device);
};

View File

@ -33,6 +33,29 @@ std::array<uint32, 6> CGamePadUtils::GetDeviceID(libevdev* dev)
}
return device;
}
#elif defined(__APPLE__)
int CGamePadUtils::GetIntProperty(IOHIDDeviceRef device, CFStringRef key)
{
int value = 0;
CFNumberRef ref = static_cast<CFNumberRef>(IOHIDDeviceGetProperty(device, key));
CFNumberGetValue(ref, kCFNumberSInt32Type, &value);
return value;
}
std::array<uint32, 6> CGamePadUtils::GetDeviceID(IOHIDDeviceRef dev)
{
std::array<uint32, 6> device{0};
int vendor = GetIntProperty(dev, CFSTR(kIOHIDVendorIDKey));
int product = GetIntProperty(dev, CFSTR(kIOHIDProductIDKey));
int location = GetIntProperty(dev, CFSTR(kIOHIDLocationIDKey));
device.at(0) = vendor & 0xFF;
device.at(1) = (vendor >> 8) & 0xFF;
device.at(2) = product & 0xFF;
device.at(3) = (product >> 8) & 0xFF;
device.at(4) = (location >> 16) & 0xFF;
device.at(5) = (location >> 24) & 0xFF;
return device;
}
#endif
bool CGamePadUtils::ParseMAC(std::array<uint32, 6>& out, std::string const& in)

View File

@ -2,6 +2,9 @@
#ifdef HAS_LIBEVDEV
#include <libevdev.h>
#elif defined(__APPLE__)
#include <IOKit/hid/IOHIDLib.h>
#include <CoreFoundation/CoreFoundation.h>
#endif
#include <array>
#include "Types.h"
@ -11,6 +14,9 @@ class CGamePadUtils
public:
#ifdef HAS_LIBEVDEV
static std::array<uint32, 6> GetDeviceID(libevdev* dev);
#elif defined(__APPLE__)
static int GetIntProperty(IOHIDDeviceRef device, CFStringRef key);
static std::array<uint32, 6> GetDeviceID(IOHIDDeviceRef dev);
#endif
static bool ParseMAC(std::array<uint32, 6>&, std::string const&);
};

View File

@ -286,6 +286,11 @@ std::string CInputBindingManager::CSimpleBinding::GetDescription() const
return QString("Key: %1").arg(QString::number(m_keyCode)).toStdString();
}
}
#elif __APPLE__
else
{
return string_format("Key: btn-%d", m_keyCode);
}
#endif
}
@ -402,6 +407,11 @@ std::string CInputBindingManager::CSimulatedAxisBinding::GetDescription() const
desc += string_format("%d", m_key1Binding.id);
}
}
#elif __APPLE__
else
{
desc += string_format("btn-%d", m_key1Binding.id);
}
#endif
desc += "/ Key: ";
if(m_key2Binding.type == 0)
@ -421,6 +431,11 @@ std::string CInputBindingManager::CSimulatedAxisBinding::GetDescription() const
desc += string_format("%d", m_key2Binding.id);
}
}
#elif __APPLE__
else
{
desc += string_format("btn-%d", m_key2Binding.id);
}
#endif
return desc;
@ -562,17 +577,32 @@ std::string CInputBindingManager::CPovHatBinding::GetDescription() const
return key + string_format("%d", m_binding.id);
}
}
#elif __APPLE__
else
{
return key + "btn-" + string_format("%d", m_binding.id);
}
#endif
}
uint32 CInputBindingManager::CPovHatBinding::GetValue() const
{
#if defined(__APPLE__)
if(m_value == 8) return 0;
int32 normalizedRefValue = (m_refValue * 360) / 8;
int32 normalizedValue = (m_value * 360) / 8;
#else
if(m_value != m_refValue) return 0;
int32 normalizedRefValue = m_refValue / 100;
int32 normalizedValue = m_value / 100;
#endif
if(GetShortestDistanceBetweenAngles(normalizedValue, normalizedRefValue) <= 45)
{
#if defined(__APPLE__)
return 1;
#else
return m_refValue;
#endif
}
else
{

View File

@ -75,7 +75,7 @@ MainWindow::MainWindow(QWidget* parent)
MainWindow::~MainWindow()
{
CAppConfig::GetInstance().Save();
#ifdef HAS_LIBEVDEV
#if defined(HAS_LIBEVDEV) || defined(__APPLE__)
m_GPDL.reset();
#endif
if(m_virtualMachine != nullptr)
@ -118,6 +118,14 @@ void MainWindow::InitVirtualMachine()
}
};
m_GPDL = std::make_unique<CGamePadDeviceListener>(onInput);
#elif defined(__APPLE__)
auto onInput = [=](std::array<uint32, 6> device, int code, int value, IOHIDElementRef elementRef) -> void {
if(m_InputBindingManager != nullptr)
{
m_InputBindingManager->OnInputEventReceived(device, code, value);
}
};
m_GPDL = std::make_unique<CGamePadDeviceListener>(onInput);
#endif
m_statsManager = new CStatsManager();

View File

@ -17,6 +17,8 @@
#ifdef HAS_LIBEVDEV
#include "GamePad/GamePadInputEventListener.h"
#include "GamePad/GamePadDeviceListener.h"
#else
#include "GamePad/GamePadDeviceListener_OSX.h"
#endif
#include "ContinuationChecker.h"
@ -65,7 +67,7 @@ private:
CPS2VM* m_virtualMachine = nullptr;
bool m_deactivatePause = false;
bool m_pauseFocusLost = true;
#ifdef HAS_LIBEVDEV
#if defined(HAS_LIBEVDEV) || defined(__APPLE__)
std::unique_ptr<CGamePadDeviceListener> m_GPDL;
#endif
enum BootType