mirror of https://github.com/bsnes-emu/bsnes.git
271 lines
9.8 KiB
C++
271 lines
9.8 KiB
C++
#pragma once
|
|
|
|
#include <IOKit/hid/IOHIDLib.h>
|
|
|
|
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::Joypad> hid{new HID::Joypad};
|
|
|
|
IOHIDDeviceRef device = nullptr;
|
|
vector<IOHIDElementRef> axes;
|
|
vector<IOHIDElementRef> hats;
|
|
vector<IOHIDElementRef> buttons;
|
|
};
|
|
vector<Joypad> joypads;
|
|
IOHIDManagerRef manager = nullptr;
|
|
|
|
enum : int { Center = 0, Up = -1, Down = +1, Left = -1, Right = +1 };
|
|
|
|
auto assign(shared_pointer<HID::Joypad> 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<shared_pointer<HID::Device>>& 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);
|
|
}
|