[MacOS] Implement joystick support

Maniphest Tasks: T508
This commit is contained in:
Roland Rossgotterer
2019-01-10 14:01:36 +01:00
committed by Mat Sutcliffe
parent ab9131fdec
commit cc620698c6
8 changed files with 350 additions and 34 deletions

View File

@@ -35,7 +35,6 @@ unix:!macx {
macx {
HEADERS += $$PWD/macos/*.h
SOURCES += $$PWD/macos/*.cpp
OBJECTIVE_SOURCES += $$PWD/macos/*.mm
LIBS += -framework CoreFoundation -framework ApplicationServices -framework Foundation -framework AppKit
}

View File

@@ -35,7 +35,7 @@ namespace BlackInput
#elif defined(Q_OS_MACOS)
std::unique_ptr<IJoystick> ptr(new CJoystickMacOS(parent));
#endif
ptr->init();
return ptr;
}

View File

@@ -39,6 +39,10 @@ namespace BlackInput
signals:
//! Joystick button combination has changed
void buttonCombinationChanged(const BlackMisc::Input::CHotkeyCombination &);
protected:
//! Initializes the platform joystick devices
virtual bool init() { return false; }
};
}

View File

@@ -1,25 +0,0 @@
/* Copyright (C) 2014
* swift Project Community / Contributors
*
* This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level
* directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project,
* including this file, may be copied, modified, propagated, or distributed except according to the terms
* contained in the LICENSE file.
*/
#include "joystickmacos.h"
using namespace BlackMisc::Input;
namespace BlackInput
{
CJoystickMacOS::CJoystickMacOS(QObject *parent) :
IJoystick(parent)
{
}
CJoystickMacOS::~CJoystickMacOS()
{
}
} // namespace BlackInput

View File

@@ -1,4 +1,4 @@
/* Copyright (C) 2014
/* Copyright (C) 2019
* swift Project Community / Contributors
*
* This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level
@@ -13,33 +13,91 @@
//! \file
#include "blackinput/joystick.h"
#include "blackmisc/input/joystickbutton.h"
#include "blackmisc/collection.h"
#include <QHash>
#include <IOKit/hid/IOHIDManager.h>
namespace BlackInput
{
//! Joystick device
class CJoystickDevice : public QObject
{
Q_OBJECT
public:
//! Constructor
CJoystickDevice(QObject *parent = nullptr);
//! Destructor
virtual ~CJoystickDevice() override;
//! Initialize device
bool init(const IOHIDDeviceRef device);
//! Return the native IOHIDDeviceRef
IOHIDDeviceRef getNativeDevice() const { return m_deviceRef; }
signals:
//! Joystick button changed
void buttonChanged(const QString &name, int index, bool isPressed);
private:
friend bool operator == (const CJoystickDevice &lhs, const CJoystickDevice &rhs);
//! Poll the device buttons
void pollDeviceState();
void processButtonEvent(IOHIDValueRef value);
static void valueCallback(void *context, IOReturn result, void *sender, IOHIDValueRef value);
QString m_deviceName = "unknown"; //!< Device name
// IOHIDDeviceRef is owned by IOHIDManager. Do not release it.
IOHIDDeviceRef m_deviceRef = nullptr;
QHash<IOHIDElementRef, int> m_joystickDeviceInputs;
};
//! MacOS implemenation of IJoystick
//! \todo Not implmeneted yet
class CJoystickMacOS : public IJoystick
{
Q_OBJECT
public:
//! Copy Constructor
CJoystickMacOS(CJoystickMacOS const &) = delete;
//! Assignment operator
CJoystickMacOS &operator=(CJoystickMacOS const &) = delete;
//! \brief Destructor
virtual ~CJoystickMacOS();
//! Destructor
virtual ~CJoystickMacOS() override;
protected:
virtual bool init() override;
private:
friend class IJoystick;
//! Destructor
CJoystickMacOS(QObject *parent = nullptr);
//! Add new joystick device
void addJoystickDevice(const IOHIDDeviceRef device);
//! Remove joystick device
void removeJoystickDevice(const IOHIDDeviceRef device);
void joystickButtonChanged(const QString &name, int index, bool isPressed);
static void matchCallback(void* context, IOReturn result, void* sender, IOHIDDeviceRef device);
static void removeCallback(void* context, IOReturn result, void* sender, IOHIDDeviceRef device);
IOHIDManagerRef m_hidManager = nullptr;
QVector<CJoystickDevice *> m_joystickDevices; //!< Joystick devices
BlackMisc::Input::CHotkeyCombination m_buttonCombination;
};
} // namespace BlackInput

View File

@@ -0,0 +1,213 @@
/* Copyright (C) 2019
* swift Project Community / Contributors
*
* This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level
* directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project,
* including this file, may be copied, modified, propagated, or distributed except according to the terms
* contained in the LICENSE file.
*/
#include "joystickmacos.h"
#include "macosinpututils.h"
#include "blackmisc/logmessage.h"
#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/hid/IOHIDUsageTables.h>
#include <array>
using namespace BlackMisc;
namespace BlackInput
{
CJoystickDevice::CJoystickDevice(QObject *parent) :
QObject(parent)
{ }
CJoystickDevice::~CJoystickDevice()
{ }
bool CJoystickDevice::init(const IOHIDDeviceRef device)
{
m_deviceRef = device;
CFTypeRef property = IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey));
m_deviceName = QString::fromCFString(static_cast<CFStringRef>(property));
CLogMessage(static_cast<CJoystickMacOS *>(nullptr)).debug() << "Found joystick device" << m_deviceName;
CFArrayRef elements = IOHIDDeviceCopyMatchingElements(device, nullptr, kIOHIDOptionsTypeNone);
for (int i = 0; i < CFArrayGetCount(elements); i++)
{
IOHIDElementRef elementRef = (IOHIDElementRef)CFArrayGetValueAtIndex(elements, i);
if (CFGetTypeID(elementRef) != IOHIDElementGetTypeID()) { continue; }
const IOHIDElementType type = IOHIDElementGetType(elementRef);
if (type != kIOHIDElementTypeInput_Button) { continue; }
const uint32_t page = IOHIDElementGetUsagePage(elementRef);
if (page == kHIDPage_Button)
{
CLogMessage(static_cast<CJoystickMacOS *>(nullptr)).debug() << "Found joystick button " << m_joystickDeviceInputs.size();
int size = m_joystickDeviceInputs.size();
m_joystickDeviceInputs.insert(elementRef, size);
IOHIDDeviceRegisterInputValueCallback(device, valueCallback, this);
}
}
CFRelease(elements);
// Filter devices with 0 buttons
if (m_joystickDeviceInputs.isEmpty()) { return false; }
CLogMessage(this).info(u"Created joystick device '%1' with %2 buttons") << m_deviceName << m_joystickDeviceInputs.size();
return true;
}
void CJoystickDevice::processButtonEvent(IOHIDValueRef value)
{
IOHIDElementRef element = IOHIDValueGetElement(value);
int buttonNumber = m_joystickDeviceInputs.value(element, -1);
if (buttonNumber != -1)
{
bool isPressed = IOHIDValueGetIntegerValue(value) == 1;
if (isPressed) { emit buttonChanged(m_deviceName, buttonNumber, true); }
else { emit buttonChanged(m_deviceName, buttonNumber, false); }
}
}
void CJoystickDevice::valueCallback(void *context, IOReturn result, void *sender, IOHIDValueRef value)
{
Q_UNUSED(result);
Q_UNUSED(sender);
CJoystickDevice *obj = static_cast<CJoystickDevice *>(context);
obj->processButtonEvent(value);
}
CJoystickMacOS::CJoystickMacOS(QObject *parent) : IJoystick(parent)
{ }
CJoystickMacOS::~CJoystickMacOS()
{
for (CJoystickDevice *d : m_joystickDevices)
{
delete d;
}
m_joystickDevices.clear();
if (m_hidManager)
{
IOHIDManagerClose(m_hidManager, kIOHIDOptionsTypeNone);
CFRelease(m_hidManager);
}
}
bool CJoystickMacOS::init()
{
m_hidManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
CFMutableArrayRef matchingArray = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
if (!matchingArray)
{
CLogMessage(this).warning(u"Cocoa: Failed to create array");
return false;
}
CFDictionaryRef matchingDict = CMacOSInputUtils::createDeviceMatchingDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick);
if (matchingDict)
{
CFArrayAppendValue(matchingArray, matchingDict);
CFRelease(matchingDict);
}
matchingDict = CMacOSInputUtils::createDeviceMatchingDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad);
if (matchingDict)
{
CFArrayAppendValue(matchingArray, matchingDict);
CFRelease(matchingDict);
}
matchingDict = CMacOSInputUtils::createDeviceMatchingDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController);
if (matchingDict)
{
CFArrayAppendValue(matchingArray, matchingDict);
CFRelease(matchingDict);
}
IOHIDManagerSetDeviceMatchingMultiple(m_hidManager, matchingArray);
CFRelease(matchingArray);
IOHIDManagerRegisterDeviceMatchingCallback(m_hidManager, matchCallback, this);
IOHIDManagerRegisterDeviceRemovalCallback(m_hidManager, removeCallback, this);
IOHIDManagerScheduleWithRunLoop(m_hidManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
return IOHIDManagerOpen(m_hidManager, kIOHIDOptionsTypeNone) == kIOReturnSuccess;
}
void CJoystickMacOS::addJoystickDevice(const IOHIDDeviceRef device)
{
for (const CJoystickDevice *d : m_joystickDevices)
{
if (d->getNativeDevice() == device) { return; }
}
CJoystickDevice *joystickDevice = new CJoystickDevice(this);
bool success = joystickDevice->init(device);
if (success)
{
connect(joystickDevice, &CJoystickDevice::buttonChanged, this, &CJoystickMacOS::joystickButtonChanged);
m_joystickDevices.push_back(joystickDevice);
}
else
{
delete joystickDevice;
}
}
void CJoystickMacOS::removeJoystickDevice(const IOHIDDeviceRef device)
{
for (auto it = m_joystickDevices.begin(); it != m_joystickDevices.end();)
{
CJoystickDevice *d = *it;
if (d->getNativeDevice() == device)
{
delete d;
it = m_joystickDevices.erase(it);
}
else
{
++it;
}
}
}
void CJoystickMacOS::joystickButtonChanged(const QString &name, int index, bool isPressed)
{
BlackMisc::Input::CHotkeyCombination oldCombination(m_buttonCombination);
if (isPressed) { m_buttonCombination.addJoystickButton({name, index}); }
else { m_buttonCombination.removeJoystickButton({name, index}); }
if (oldCombination != m_buttonCombination)
{
emit buttonCombinationChanged(m_buttonCombination);
}
}
void CJoystickMacOS::matchCallback(void *context, IOReturn result, void *sender, IOHIDDeviceRef device)
{
Q_UNUSED(result);
Q_UNUSED(sender);
CJoystickMacOS *obj = static_cast<CJoystickMacOS *>(context);
obj->addJoystickDevice(device);
}
void CJoystickMacOS::removeCallback(void *context, IOReturn result, void *sender, IOHIDDeviceRef device)
{
Q_UNUSED(result);
Q_UNUSED(sender);
CJoystickMacOS *obj = static_cast<CJoystickMacOS *>(context);
obj->removeJoystickDevice(device);
}
} // namespace BlackInput

View File

@@ -0,0 +1,30 @@
/* Copyright (C) 2019
* swift Project Community / Contributors
*
* This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level
* directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project,
* including this file, may be copied, modified, propagated, or distributed except according to the terms
* contained in the LICENSE file.
*/
#ifndef BLACKINPUT_MACOSINPUTUTILS_H
#define BLACKINPUT_MACOSINPUTUTILS_H
//! \file
#include <CoreFoundation/CoreFoundation.h>
namespace BlackInput
{
//! Common MacOS input utilities
class CMacOSInputUtils
{
public:
CMacOSInputUtils() = delete;
//! Creates a new device matching dict using usagePage and usage
static CFMutableDictionaryRef createDeviceMatchingDictionary(UInt32 usagePage, UInt32 usage);
};
}
#endif

View File

@@ -0,0 +1,37 @@
#include "macosinpututils.h"
#include <IOKit/hid/IOHIDKeys.h>
namespace BlackInput
{
CFMutableDictionaryRef CMacOSInputUtils::createDeviceMatchingDictionary(UInt32 usagePage, UInt32 usage)
{
CFMutableDictionaryRef result = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (result)
{
if (usagePage)
{
CFNumberRef pageCFNumberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usagePage);
if (pageCFNumberRef)
{
CFDictionarySetValue(result, CFSTR(kIOHIDDeviceUsagePageKey), pageCFNumberRef);
CFRelease(pageCFNumberRef);
if (usage)
{
CFNumberRef usageCFNumberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage);
if (usageCFNumberRef)
{
CFDictionarySetValue(result, CFSTR(kIOHIDDeviceUsageKey), usageCFNumberRef);
CFRelease(usageCFNumberRef);
}
}
}
}
}
return result;
}
}