mirror of
https://github.com/swift-project/pilotclient.git
synced 2026-03-22 23:05:36 +08:00
[MacOS] Implement joystick support
Maniphest Tasks: T508
This commit is contained in:
committed by
Mat Sutcliffe
parent
ab9131fdec
commit
cc620698c6
@@ -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
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace BlackInput
|
||||
#elif defined(Q_OS_MACOS)
|
||||
std::unique_ptr<IJoystick> ptr(new CJoystickMacOS(parent));
|
||||
#endif
|
||||
|
||||
ptr->init();
|
||||
return ptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -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; }
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
213
src/blackinput/macos/joystickmacos.mm
Normal file
213
src/blackinput/macos/joystickmacos.mm
Normal 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
|
||||
30
src/blackinput/macos/macosinpututils.h
Normal file
30
src/blackinput/macos/macosinpututils.h
Normal 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
|
||||
37
src/blackinput/macos/macosinpututils.mm
Normal file
37
src/blackinput/macos/macosinpututils.mm
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user