Play-/Source/ui_qt/macos/InputProviderMacOsHid.cpp

361 lines
14 KiB
C++
Raw Permalink Normal View History

#include "InputProviderMacOsHid.h"
#include "string_format.h"
#define PROVIDER_ID 'mHID'
static int GetIntProperty(IOHIDDeviceRef device, CFStringRef key)
{
int value = 0;
CFNumberRef ref = static_cast<CFNumberRef>(IOHIDDeviceGetProperty(device, key));
CFNumberGetValue(ref, kCFNumberSInt32Type, &value);
return value;
}
static DeviceIdType GetDeviceID(IOHIDDeviceRef dev)
{
DeviceIdType 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;
}
static BINDINGTARGET::KEYTYPE GetKeyType(uint32 usage, IOHIDElementType type)
{
bool is_axis = (type == kIOHIDElementTypeInput_Axis) || (type == kIOHIDElementTypeInput_Misc);
auto keyType = BINDINGTARGET::KEYTYPE::BUTTON;
if(is_axis)
{
keyType = BINDINGTARGET::KEYTYPE::AXIS;
if(usage == kHIDUsage_GD_Hatswitch)
{
keyType = BINDINGTARGET::KEYTYPE::POVHAT;
}
}
return keyType;
}
CInputProviderMacOsHid::CInputProviderMacOsHid()
: m_running(true)
{
m_thread = std::thread([this]() { InputDeviceListenerThread(); });
}
CInputProviderMacOsHid::~CInputProviderMacOsHid()
{
m_running = false;
if(m_thread.joinable())
{
m_thread.join();
}
}
uint32 CInputProviderMacOsHid::GetId() const
{
return PROVIDER_ID;
}
std::string CInputProviderMacOsHid::GetTargetDescription(const BINDINGTARGET& target) const
{
return string_format("HID: btn-%d", target.keyId);
}
CFMutableDictionaryRef CInputProviderMacOsHid::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 CInputProviderMacOsHid::OnDeviceMatchedStub(void* context, IOReturn result, void* sender, IOHIDDeviceRef device)
{
reinterpret_cast<CInputProviderMacOsHid*>(context)->OnDeviceMatched(result, sender, device);
}
void CInputProviderMacOsHid::InputValueCallbackStub(void* context, IOReturn result, void* sender, IOHIDValueRef value)
{
auto deviceInfo = reinterpret_cast<DEVICE_INFO*>(context);
deviceInfo->provider->InputValueCallback(deviceInfo, result, sender, value);
}
void CInputProviderMacOsHid::InputReportCallbackStub_DS3(void* context, IOReturn result, void* sender, IOHIDReportType type, uint32_t reportID, uint8_t* report, CFIndex reportLength)
{
auto deviceInfo = reinterpret_cast<DEVICE_INFO*>(context);
deviceInfo->provider->InputReportCallback_DS3(deviceInfo, result, sender, type, reportID, report, reportLength);
}
void CInputProviderMacOsHid::InputReportCallbackStub_DS4(void* context, IOReturn result, void* sender, IOHIDReportType type, uint32_t reportID, uint8_t* report, CFIndex reportLength)
{
auto deviceInfo = reinterpret_cast<DEVICE_INFO*>(context);
deviceInfo->provider->InputReportCallback_DS4(deviceInfo, result, sender, type, reportID, report, reportLength);
}
void CInputProviderMacOsHid::OnDeviceMatched(IOReturn result, void* sender, IOHIDDeviceRef device)
{
m_devices.push_back(DEVICE_INFO());
auto& deviceInfo = *m_devices.rbegin();
deviceInfo.provider = this;
deviceInfo.device = device;
deviceInfo.deviceId = GetDeviceID(device);
2018-11-28 22:32:27 -05:00
auto InputReportCallbackStub = GetCallback(device);
if(InputReportCallbackStub)
{
uint32_t max_input_report_size = GetIntProperty(device, CFSTR(kIOHIDMaxInputReportSizeKey));
uint8_t* report_buffer = static_cast<uint8_t*>(calloc(max_input_report_size, sizeof(uint8_t)));
IOHIDDeviceRegisterInputReportCallback(device, report_buffer, max_input_report_size, InputReportCallbackStub, &deviceInfo);
}
else
{
SetInitialBindValues(device);
IOHIDDeviceRegisterInputValueCallback(device, &InputValueCallbackStub, &deviceInfo);
}
}
void CInputProviderMacOsHid::InputValueCallback(DEVICE_INFO* deviceInfo, IOReturn result, void* sender, IOHIDValueRef valueRef)
{
IOHIDElementRef elementRef = IOHIDValueGetElement(valueRef);
uint32 usagePage = IOHIDElementGetUsagePage(elementRef);
if(
2018-11-28 22:32:27 -05:00
(usagePage != kHIDPage_GenericDesktop) &&
(usagePage != kHIDPage_Button))
{
return;
}
uint32 usage = IOHIDElementGetUsage(elementRef);
CFIndex value = IOHIDValueGetIntegerValue(valueRef);
IOHIDElementType type = IOHIDElementGetType(elementRef);
BINDINGTARGET tgt;
tgt.providerId = PROVIDER_ID;
tgt.deviceId = deviceInfo->deviceId;
tgt.keyId = usage;
tgt.keyType = GetKeyType(usage, type);
OnInput(tgt, value);
}
void CInputProviderMacOsHid::InputReportCallback_DS3(DEVICE_INFO* deviceInfo, IOReturn result, void* sender, IOHIDReportType type, uint32_t reportID, uint8_t* report, CFIndex reportLength)
{
if(report[0] != 0x1 || report[1] == 0xff)
return;
struct PS3Btn* new_btn_state = reinterpret_cast<struct PS3Btn*>(&report[2]);
struct PS3Btn* prev_btn_state = reinterpret_cast<struct PS3Btn*>(deviceInfo->prev_btn_state);
int is_change = 0;
2018-11-28 22:32:27 -05:00
#define checkbtnstate(prev_btn_state, new_btn_state, btn, btn_id, type) \
if(deviceInfo->first_run || (prev_btn_state->btn != new_btn_state->btn)) \
2018-11-28 22:32:27 -05:00
{ \
is_change += 1; \
BINDINGTARGET tgt; \
tgt.providerId = PROVIDER_ID; \
tgt.deviceId = deviceInfo->deviceId; \
tgt.keyId = btn_id; \
tgt.keyType = type; \
OnInput(tgt, new_btn_state->btn); \
}
deviceInfo->first_run = false;
checkbtnstate(prev_btn_state, new_btn_state, Select, 1, BINDINGTARGET::KEYTYPE::BUTTON);
checkbtnstate(prev_btn_state, new_btn_state, L3, 2, BINDINGTARGET::KEYTYPE::BUTTON);
checkbtnstate(prev_btn_state, new_btn_state, R3, 3, BINDINGTARGET::KEYTYPE::BUTTON);
checkbtnstate(prev_btn_state, new_btn_state, Start, 4, BINDINGTARGET::KEYTYPE::BUTTON);
checkbtnstate(prev_btn_state, new_btn_state, DPadU, 5, BINDINGTARGET::KEYTYPE::BUTTON);
checkbtnstate(prev_btn_state, new_btn_state, DPadR, 6, BINDINGTARGET::KEYTYPE::BUTTON);
checkbtnstate(prev_btn_state, new_btn_state, DPadD, 7, BINDINGTARGET::KEYTYPE::BUTTON);
checkbtnstate(prev_btn_state, new_btn_state, DPadL, 8, BINDINGTARGET::KEYTYPE::BUTTON);
checkbtnstate(prev_btn_state, new_btn_state, R2, 9, BINDINGTARGET::KEYTYPE::BUTTON);
checkbtnstate(prev_btn_state, new_btn_state, L2, 10, BINDINGTARGET::KEYTYPE::BUTTON);
checkbtnstate(prev_btn_state, new_btn_state, R1, 11, BINDINGTARGET::KEYTYPE::BUTTON);
checkbtnstate(prev_btn_state, new_btn_state, L1, 12, BINDINGTARGET::KEYTYPE::BUTTON);
checkbtnstate(prev_btn_state, new_btn_state, PSHome, 13, BINDINGTARGET::KEYTYPE::BUTTON);
checkbtnstate(prev_btn_state, new_btn_state, LX, 14, BINDINGTARGET::KEYTYPE::AXIS);
checkbtnstate(prev_btn_state, new_btn_state, LY, 15, BINDINGTARGET::KEYTYPE::AXIS);
checkbtnstate(prev_btn_state, new_btn_state, RX, 16, BINDINGTARGET::KEYTYPE::AXIS);
checkbtnstate(prev_btn_state, new_btn_state, RY, 17, BINDINGTARGET::KEYTYPE::AXIS);
//checkbtnstate(prev_btn_state, new_btn_state, L2T, 18, 3);
//checkbtnstate(prev_btn_state, new_btn_state, R2T, 19, 3);
//checkbtnstate(prev_btn_state, new_btn_state, L1T, 20, 3);
//checkbtnstate(prev_btn_state, new_btn_state, R1T, 21, 3);
checkbtnstate(prev_btn_state, new_btn_state, Triangle, 22, BINDINGTARGET::KEYTYPE::BUTTON);
checkbtnstate(prev_btn_state, new_btn_state, Circle, 23, BINDINGTARGET::KEYTYPE::BUTTON);
checkbtnstate(prev_btn_state, new_btn_state, Cross, 24, BINDINGTARGET::KEYTYPE::BUTTON);
checkbtnstate(prev_btn_state, new_btn_state, Square, 25, BINDINGTARGET::KEYTYPE::BUTTON);
#undef checkbtnstate
if(is_change > 0)
{
memcpy(deviceInfo->prev_btn_state, new_btn_state, sizeof(struct PS3Btn));
}
}
void CInputProviderMacOsHid::InputReportCallback_DS4(DEVICE_INFO* deviceInfo, IOReturn result, void* sender, IOHIDReportType type, uint32_t reportID, uint8_t* report, CFIndex reportLength)
{
int offset = report[0] == 1 ? 1 : 3;
struct PS4Btn* new_btn_state = reinterpret_cast<struct PS4Btn*>(&report[offset]);
struct PS4Btn* prev_btn_state = reinterpret_cast<struct PS4Btn*>(deviceInfo->prev_btn_state);
int is_change = 0;
2018-11-28 22:32:27 -05:00
#define checkbtnstate(prev_btn_state, new_btn_state, btn, btn_id, type) \
if(deviceInfo->first_run || (prev_btn_state->btn != new_btn_state->btn)) \
2018-11-28 22:32:27 -05:00
{ \
is_change += 1; \
BINDINGTARGET tgt; \
tgt.providerId = PROVIDER_ID; \
tgt.deviceId = deviceInfo->deviceId; \
tgt.keyId = btn_id; \
tgt.keyType = type; \
OnInput(tgt, new_btn_state->btn); \
}
deviceInfo->first_run = false;
checkbtnstate(prev_btn_state, new_btn_state, LX, 1, BINDINGTARGET::KEYTYPE::AXIS);
checkbtnstate(prev_btn_state, new_btn_state, LY, 2, BINDINGTARGET::KEYTYPE::AXIS);
checkbtnstate(prev_btn_state, new_btn_state, RX, 3, BINDINGTARGET::KEYTYPE::AXIS);
checkbtnstate(prev_btn_state, new_btn_state, RY, 4, BINDINGTARGET::KEYTYPE::AXIS);
checkbtnstate(prev_btn_state, new_btn_state, DPad, 5, BINDINGTARGET::KEYTYPE::POVHAT);
checkbtnstate(prev_btn_state, new_btn_state, Triangle, 6, BINDINGTARGET::KEYTYPE::BUTTON);
checkbtnstate(prev_btn_state, new_btn_state, Circle, 7, BINDINGTARGET::KEYTYPE::BUTTON);
checkbtnstate(prev_btn_state, new_btn_state, Cross, 8, BINDINGTARGET::KEYTYPE::BUTTON);
checkbtnstate(prev_btn_state, new_btn_state, Square, 9, BINDINGTARGET::KEYTYPE::BUTTON);
checkbtnstate(prev_btn_state, new_btn_state, L1, 10, BINDINGTARGET::KEYTYPE::BUTTON);
checkbtnstate(prev_btn_state, new_btn_state, R1, 11, BINDINGTARGET::KEYTYPE::BUTTON);
//checkbtnstate(prev_btn_state, new_btn_state, L2, 12, 1);
//checkbtnstate(prev_btn_state, new_btn_state, R2, 13, 1);
checkbtnstate(prev_btn_state, new_btn_state, Share, 14, BINDINGTARGET::KEYTYPE::BUTTON);
checkbtnstate(prev_btn_state, new_btn_state, Option, 15, BINDINGTARGET::KEYTYPE::BUTTON);
checkbtnstate(prev_btn_state, new_btn_state, L3, 16, BINDINGTARGET::KEYTYPE::BUTTON);
checkbtnstate(prev_btn_state, new_btn_state, R3, 17, BINDINGTARGET::KEYTYPE::BUTTON);
checkbtnstate(prev_btn_state, new_btn_state, PSHome, 18, BINDINGTARGET::KEYTYPE::BUTTON);
checkbtnstate(prev_btn_state, new_btn_state, TouchPad, 19, BINDINGTARGET::KEYTYPE::BUTTON);
checkbtnstate(prev_btn_state, new_btn_state, LT, 20, BINDINGTARGET::KEYTYPE::AXIS);
checkbtnstate(prev_btn_state, new_btn_state, RT, 21, BINDINGTARGET::KEYTYPE::AXIS);
#undef checkbtnstate
if(is_change > 0)
{
memcpy(deviceInfo->prev_btn_state, new_btn_state, sizeof(struct PS4Btn));
}
}
IOHIDReportCallback CInputProviderMacOsHid::GetCallback(IOHIDDeviceRef device)
{
auto vid = GetIntProperty(device, CFSTR(kIOHIDVendorIDKey));
auto pid = GetIntProperty(device, CFSTR(kIOHIDProductIDKey));
if(vid == 0x54C)
{
if(pid == 0x9CC || pid == 0x5C4)
{
return &InputReportCallbackStub_DS4;
}
else if(pid == 0x268)
{
return &InputReportCallbackStub_DS3;
}
}
return nullptr;
}
void CInputProviderMacOsHid::SetInitialBindValues(IOHIDDeviceRef device)
{
CFArrayRef elements = IOHIDDeviceCopyMatchingElements(device, nullptr, 0);
2018-11-28 22:32:27 -05:00
for(int i = 0; i < CFArrayGetCount(elements); i++)
{
IOHIDElementRef elementRef = (IOHIDElementRef)CFArrayGetValueAtIndex(elements, i);
uint32 usagePage = IOHIDElementGetUsagePage(elementRef);
if(
2018-11-28 22:32:27 -05:00
(usagePage != kHIDPage_GenericDesktop) &&
(usagePage != kHIDPage_Button))
{
continue;
}
IOHIDValueRef valueRef;
if(IOHIDDeviceGetValue(device, elementRef, &valueRef) != kIOReturnSuccess)
{
continue;
}
2018-11-28 22:32:27 -05:00
CFIndex value = IOHIDValueGetIntegerValue(valueRef);
IOHIDElementType type = IOHIDElementGetType(elementRef);
uint32 usage = IOHIDElementGetUsage(elementRef);
BINDINGTARGET tgt;
tgt.providerId = PROVIDER_ID;
tgt.deviceId = GetDeviceID(device);
tgt.keyId = usage;
tgt.keyType = GetKeyType(usage, type);
switch(type)
{
2018-11-28 22:32:27 -05:00
case kIOHIDElementTypeInput_Misc:
case kIOHIDElementTypeInput_Button:
case kIOHIDElementTypeInput_Axis:
OnInput(tgt, value);
break;
default:
break;
}
}
}
void CInputProviderMacOsHid::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, OnDeviceMatchedStub, this);
IOHIDManagerOpen(m_hidManager, kIOHIDOptionsTypeNone);
IOHIDManagerScheduleWithRunLoop(m_hidManager, CFRunLoopGetCurrent(), CFSTR("CustomLoop"));
while(CFRunLoopRunInMode(CFSTR("CustomLoop"), 1, true) != kCFRunLoopRunStopped && m_running)
{
}
IOHIDManagerClose(m_hidManager, 0);
}