diff --git a/src/blackinput/blackinput.pro b/src/blackinput/blackinput.pro index f8ceb50e4..74348e88f 100644 --- a/src/blackinput/blackinput.pro +++ b/src/blackinput/blackinput.pro @@ -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 } diff --git a/src/blackinput/joystick.cpp b/src/blackinput/joystick.cpp index c42273251..f35148831 100644 --- a/src/blackinput/joystick.cpp +++ b/src/blackinput/joystick.cpp @@ -35,7 +35,7 @@ namespace BlackInput #elif defined(Q_OS_MACOS) std::unique_ptr ptr(new CJoystickMacOS(parent)); #endif - + ptr->init(); return ptr; } diff --git a/src/blackinput/joystick.h b/src/blackinput/joystick.h index 0b4c8dd40..8d17f60c1 100644 --- a/src/blackinput/joystick.h +++ b/src/blackinput/joystick.h @@ -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; } }; } diff --git a/src/blackinput/macos/joystickmacos.cpp b/src/blackinput/macos/joystickmacos.cpp deleted file mode 100644 index 6e0e4a32f..000000000 --- a/src/blackinput/macos/joystickmacos.cpp +++ /dev/null @@ -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 diff --git a/src/blackinput/macos/joystickmacos.h b/src/blackinput/macos/joystickmacos.h index b94085fd4..186fae420 100644 --- a/src/blackinput/macos/joystickmacos.h +++ b/src/blackinput/macos/joystickmacos.h @@ -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 +#include 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 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 m_joystickDevices; //!< Joystick devices + + BlackMisc::Input::CHotkeyCombination m_buttonCombination; }; } // namespace BlackInput diff --git a/src/blackinput/macos/joystickmacos.mm b/src/blackinput/macos/joystickmacos.mm new file mode 100644 index 000000000..3ff6d0e63 --- /dev/null +++ b/src/blackinput/macos/joystickmacos.mm @@ -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 +#include + +#include + +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(property)); + + CLogMessage(static_cast(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(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(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(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(context); + obj->removeJoystickDevice(device); + } + +} // namespace BlackInput diff --git a/src/blackinput/macos/macosinpututils.h b/src/blackinput/macos/macosinpututils.h new file mode 100644 index 000000000..0273aff08 --- /dev/null +++ b/src/blackinput/macos/macosinpututils.h @@ -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 + +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 diff --git a/src/blackinput/macos/macosinpututils.mm b/src/blackinput/macos/macosinpututils.mm new file mode 100644 index 000000000..a7ffc5dcc --- /dev/null +++ b/src/blackinput/macos/macosinpututils.mm @@ -0,0 +1,37 @@ +#include "macosinpututils.h" + +#include + +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; + } +}