#pragma once #include auto deviceMatchingCallback(void* context, IOReturn result, void* sender, IOHIDDeviceRef device) -> void; auto deviceRemovalCallback(void* context, IOReturn result, void* sender, IOHIDDeviceRef device) -> void; struct InputJoypadIOKit { Input& input; InputJoypadIOKit(Input& input) : input(input) {} struct Joypad { auto appendElements(CFArrayRef elements) -> void { for(uint n : range(CFArrayGetCount(elements))) { IOHIDElementRef element = (IOHIDElementRef)CFArrayGetValueAtIndex(elements, n); IOHIDElementType type = IOHIDElementGetType(element); uint32_t page = IOHIDElementGetUsagePage(element); uint32_t usage = IOHIDElementGetUsage(element); switch(type) { case kIOHIDElementTypeInput_Button: appendButton(element); break; case kIOHIDElementTypeInput_Axis: case kIOHIDElementTypeInput_Misc: if(page != kHIDPage_GenericDesktop && page != kHIDPage_Simulation) break; if(usage == kHIDUsage_Sim_Accelerator || usage == kHIDUsage_Sim_Brake || usage == kHIDUsage_Sim_Rudder || usage == kHIDUsage_Sim_Throttle || usage == kHIDUsage_GD_X || usage == kHIDUsage_GD_Y || usage == kHIDUsage_GD_Z || usage == kHIDUsage_GD_Rx || usage == kHIDUsage_GD_Ry || usage == kHIDUsage_GD_Rz || usage == kHIDUsage_GD_Slider || usage == kHIDUsage_GD_Dial || usage == kHIDUsage_GD_Wheel ) appendAxis(element); if(usage == kHIDUsage_GD_DPadUp || usage == kHIDUsage_GD_DPadDown || usage == kHIDUsage_GD_DPadLeft || usage == kHIDUsage_GD_DPadRight || usage == kHIDUsage_GD_Start || usage == kHIDUsage_GD_Select || usage == kHIDUsage_GD_SystemMainMenu ) appendButton(element); if(usage == kHIDUsage_GD_Hatswitch ) appendHat(element); break; case kIOHIDElementTypeCollection: if(CFArrayRef children = IOHIDElementGetChildren(element)) appendElements(children); break; } } } auto appendAxis(IOHIDElementRef element) -> void { IOHIDElementCookie cookie = IOHIDElementGetCookie(element); if(auto duplicate = axes.find([cookie](auto axis) { return IOHIDElementGetCookie(axis) == cookie; })) { return; } int min = IOHIDElementGetLogicalMin(element); int max = IOHIDElementGetLogicalMax(element); int range = max - min; if(range == 0) return; hid->axes().append(axes.size()); axes.append(element); } auto appendHat(IOHIDElementRef element) -> void { IOHIDElementCookie cookie = IOHIDElementGetCookie(element); if(auto duplicate = hats.find([cookie](auto hat) { return IOHIDElementGetCookie(hat) == cookie; })) { return; } uint n = hats.size() * 2; hid->hats().append(n + 0); hid->hats().append(n + 1); hats.append(element); } auto appendButton(IOHIDElementRef element) -> void { IOHIDElementCookie cookie = IOHIDElementGetCookie(element); if(auto duplicate = buttons.find([cookie](auto button) { return IOHIDElementGetCookie(button) == cookie; })) { return; } hid->buttons().append(buttons.size()); buttons.append(element); } shared_pointer hid{new HID::Joypad}; IOHIDDeviceRef device = nullptr; vector axes; vector hats; vector buttons; }; vector joypads; IOHIDManagerRef manager = nullptr; enum : int { Center = 0, Up = -1, Down = +1, Left = -1, Right = +1 }; auto assign(shared_pointer hid, uint groupID, uint inputID, int16_t value) -> void { auto& group = hid->group(groupID); if(group.input(inputID).value() == value) return; input.doChange(hid, groupID, inputID, group.input(inputID).value(), value); group.input(inputID).setValue(value); } auto poll(vector>& devices) -> void { for(auto& jp : joypads) { IOHIDDeviceRef device = jp.device; for(uint n : range(jp.axes.size())) { int value = 0; IOHIDValueRef valueRef; if(IOHIDDeviceGetValue(device, jp.axes[n], &valueRef) == kIOReturnSuccess) { int min = IOHIDElementGetLogicalMin(jp.axes[n]); int max = IOHIDElementGetLogicalMax(jp.axes[n]); int range = max - min; value = (IOHIDValueGetIntegerValue(valueRef) - min) * 65535LL / range - 32767; } assign(jp.hid, HID::Joypad::GroupID::Axis, n, sclamp<16>(value)); } for(uint n : range(jp.hats.size())) { int x = Center; int y = Center; IOHIDValueRef valueRef; if(IOHIDDeviceGetValue(device, jp.hats[n], &valueRef) == kIOReturnSuccess) { int position = IOHIDValueGetIntegerValue(valueRef); int min = IOHIDElementGetLogicalMin(jp.hats[n]); int max = IOHIDElementGetLogicalMax(jp.hats[n]); if(position >= min && position <= max) { position -= min; int range = max - min + 1; if(range == 4) { position *= 2; } if(range == 8) { switch(position) { case 0: x = Up; y = Center; break; case 1: x = Up; y = Right; break; case 2: x = Center; y = Right; break; case 3: x = Down; y = Right; break; case 4: x = Down; y = Center; break; case 5: x = Down; y = Left; break; case 6: x = Center; y = Left; break; case 7: x = Up; y = Left; break; } } } } assign(jp.hid, HID::Joypad::GroupID::Hat, n * 2 + 0, x * 32767); assign(jp.hid, HID::Joypad::GroupID::Hat, n * 2 + 1, y * 32767); } for(uint n : range(jp.buttons.size())) { int value = 0; IOHIDValueRef valueRef; if(IOHIDDeviceGetValue(device, jp.buttons[n], &valueRef) == kIOReturnSuccess) { value = IOHIDValueGetIntegerValue(valueRef); } assign(jp.hid, HID::Joypad::GroupID::Button, n, (bool)value); } devices.append(jp.hid); } } auto rumble(uint64_t id, bool enable) -> bool { //todo return false; } auto initialize() -> bool { manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); if(!manager) return false; CFArrayRef matcher = createMatcher(); if(!matcher) { releaseManager(); return false; } IOHIDManagerSetDeviceMatchingMultiple(manager, matcher); IOHIDManagerRegisterDeviceMatchingCallback(manager, deviceMatchingCallback, this); IOHIDManagerRegisterDeviceRemovalCallback(manager, deviceRemovalCallback, this); IOHIDManagerScheduleWithRunLoop(manager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); if(IOHIDManagerOpen(manager, kIOHIDOptionsTypeNone) != kIOReturnSuccess) { releaseManager(); return false; } return true; } auto terminate() -> void { if(!manager) return; IOHIDManagerClose(manager, kIOHIDOptionsTypeNone); releaseManager(); } auto appendJoypad(IOHIDDeviceRef device) -> void { Joypad jp; jp.device = device; int32_t vendorID, productID; CFNumberGetValue((CFNumberRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey)), kCFNumberSInt32Type, &vendorID); CFNumberGetValue((CFNumberRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductIDKey)), kCFNumberSInt32Type, &productID); jp.hid->setVendorID(vendorID); jp.hid->setProductID(productID); CFArrayRef elements = IOHIDDeviceCopyMatchingElements(device, nullptr, kIOHIDOptionsTypeNone); if(elements) { jp.appendElements(elements); CFRelease(elements); joypads.append(jp); } } auto removeJoypad(IOHIDDeviceRef device) -> void { for(uint n : range(joypads.size())) { if(joypads[n].device == device) { joypads.remove(n); return; } } } private: auto releaseManager() -> void { CFRelease(manager); manager = nullptr; } auto createMatcher() -> CFArrayRef { CFDictionaryRef dict1 = createMatcherCriteria(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick); if(!dict1) return nullptr; CFDictionaryRef dict2 = createMatcherCriteria(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad); if(!dict2) return CFRelease(dict1), nullptr; CFDictionaryRef dict3 = createMatcherCriteria(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController); if(!dict3) return CFRelease(dict1), CFRelease(dict2), nullptr; const void* values[] = {dict1, dict2, dict3}; CFArrayRef array = CFArrayCreate(kCFAllocatorDefault, values, 3, &kCFTypeArrayCallBacks); CFRelease(dict1), CFRelease(dict2), CFRelease(dict3); return array; } auto createMatcherCriteria(uint32_t page, uint32_t usage) -> CFDictionaryRef { CFNumberRef pageNumber = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page); if(!pageNumber) return nullptr; CFNumberRef usageNumber = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage); if(!usageNumber) return CFRelease(pageNumber), nullptr; const void* keys[] = {CFSTR(kIOHIDDeviceUsagePageKey), CFSTR(kIOHIDDeviceUsageKey)}; const void* values[] = {pageNumber, usageNumber}; CFDictionaryRef dict = CFDictionaryCreate( kCFAllocatorDefault, keys, values, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks ); CFRelease(pageNumber), CFRelease(usageNumber); return dict; } }; auto deviceMatchingCallback(void* context, IOReturn result, void* sender, IOHIDDeviceRef device) -> void { ((InputJoypadIOKit*)context)->appendJoypad(device); } auto deviceRemovalCallback(void* context, IOReturn result, void* sender, IOHIDDeviceRef device) -> void { ((InputJoypadIOKit*)context)->removeJoypad(device); }