141 lines
5.6 KiB
C
Raw Normal View History

2025-01-22 16:18:30 +01:00
#include <kinc/backend/HIDManager.h>
#include <kinc/log.h>
static int initHIDManager(struct HIDManager *manager);
static bool addMatchingArray(struct HIDManager *manager, CFMutableArrayRef matchingCFArrayRef, CFDictionaryRef matchingCFDictRef);
static CFMutableDictionaryRef createDeviceMatchingDictionary(struct HIDManager *manager, uint32_t inUsagePage, uint32_t inUsage);
static void deviceConnected(void *inContext, IOReturn inResult, void *inSender, IOHIDDeviceRef inIOHIDDeviceRef);
static void deviceRemoved(void *inContext, IOReturn inResult, void *inSender, IOHIDDeviceRef inIOHIDDeviceRef);
void HIDManager_init(struct HIDManager *manager) {
manager->managerRef = 0x0;
initHIDManager(manager);
}
void HIDManager_destroy(struct HIDManager *manager) {
if (manager->managerRef) {
IOHIDManagerUnscheduleFromRunLoop(manager->managerRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
IOHIDManagerClose(manager->managerRef, kIOHIDOptionsTypeNone);
}
}
static int initHIDManager(struct HIDManager *manager) {
// Initialize the IOHIDManager
manager->managerRef = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
if (CFGetTypeID(manager->managerRef) == IOHIDManagerGetTypeID()) {
// Create a matching dictionary for gamepads and joysticks
CFMutableArrayRef matchingCFArrayRef = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
if (matchingCFArrayRef) {
// Create a device matching dictionary for joysticks
CFDictionaryRef matchingCFDictRef = createDeviceMatchingDictionary(manager, kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick);
addMatchingArray(manager, matchingCFArrayRef, matchingCFDictRef);
// Create a device matching dictionary for game pads
matchingCFDictRef = createDeviceMatchingDictionary(manager, kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad);
addMatchingArray(manager, matchingCFArrayRef, matchingCFDictRef);
}
else {
kinc_log(KINC_LOG_LEVEL_ERROR, "%s: CFArrayCreateMutable failed.", __PRETTY_FUNCTION__);
return -1;
}
// Set the HID device matching array
IOHIDManagerSetDeviceMatchingMultiple(manager->managerRef, matchingCFArrayRef);
CFRelease(matchingCFArrayRef);
// Open manager
IOHIDManagerOpen(manager->managerRef, kIOHIDOptionsTypeNone);
// Register routines to be called when (matching) devices are connected or disconnected
IOHIDManagerRegisterDeviceMatchingCallback(manager->managerRef, deviceConnected, manager);
IOHIDManagerRegisterDeviceRemovalCallback(manager->managerRef, deviceRemoved, manager);
IOHIDManagerScheduleWithRunLoop(manager->managerRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
return 0;
}
return -1;
}
bool addMatchingArray(struct HIDManager *manager, CFMutableArrayRef matchingCFArrayRef, CFDictionaryRef matchingCFDictRef) {
if (matchingCFDictRef) {
// Add it to the matching array
CFArrayAppendValue(matchingCFArrayRef, matchingCFDictRef);
CFRelease(matchingCFDictRef); // and release it
return true;
}
return false;
}
CFMutableDictionaryRef createDeviceMatchingDictionary(struct HIDManager *manager, uint32_t inUsagePage, uint32_t inUsage) {
// Create a dictionary to add usage page/usages to
CFMutableDictionaryRef result = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
if (result) {
if (inUsagePage) {
// Add key for device type to refine the matching dictionary.
CFNumberRef pageCFNumberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &inUsagePage);
if (pageCFNumberRef) {
CFDictionarySetValue(result, CFSTR(kIOHIDDeviceUsagePageKey), pageCFNumberRef);
CFRelease(pageCFNumberRef);
// note: the usage is only valid if the usage page is also defined
if (inUsage) {
CFNumberRef usageCFNumberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &inUsage);
if (usageCFNumberRef) {
CFDictionarySetValue(result, CFSTR(kIOHIDDeviceUsageKey), usageCFNumberRef);
CFRelease(usageCFNumberRef);
}
else {
kinc_log(KINC_LOG_LEVEL_ERROR, "%s: CFNumberCreate(usage) failed.", __PRETTY_FUNCTION__);
}
}
}
else {
kinc_log(KINC_LOG_LEVEL_ERROR, "%s: CFNumberCreate(usage page) failed.", __PRETTY_FUNCTION__);
}
}
}
else {
kinc_log(KINC_LOG_LEVEL_ERROR, "%s: CFDictionaryCreateMutable failed.", __PRETTY_FUNCTION__);
}
return result;
}
// HID device plugged callback
void deviceConnected(void *inContext, IOReturn inResult, void *inSender, IOHIDDeviceRef inIOHIDDeviceRef) {
// Reference manager
struct HIDManager *manager = (struct HIDManager *)inContext;
// Find an empty slot in the devices list and add the new device there
// TODO: does this need to be made thread safe?
struct HIDManagerDeviceRecord *device = &manager->devices[0];
for (int i = 0; i < KINC_MAX_HID_DEVICES; ++i, ++device) {
if (!device->connected) {
device->connected = true;
device->device = inIOHIDDeviceRef;
HIDGamepad_bind(&device->pad, inIOHIDDeviceRef, i);
break;
}
}
}
// HID device unplugged callback
void deviceRemoved(void *inContext, IOReturn inResult, void *inSender, IOHIDDeviceRef inIOHIDDeviceRef) {
// Reference manager
struct HIDManager *manager = (struct HIDManager *)inContext;
// TODO: does this need to be made thread safe?
struct HIDManagerDeviceRecord *device = &manager->devices[0];
for (int i = 0; i < KINC_MAX_HID_DEVICES; ++i, ++device) {
// TODO: is comparing IOHIDDeviceRef to match devices safe? Is there a better way?
if (device->connected && device->device == inIOHIDDeviceRef) {
device->connected = false;
device->device = NULL;
HIDGamepad_unbind(&device->pad);
break;
}
}
}