385 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
		
		
			
		
	
	
			385 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
|  | #include "HIDGamepad.h"
 | ||
|  | #include "HIDManager.h"
 | ||
|  | 
 | ||
|  | #include <kinc/error.h>
 | ||
|  | #include <kinc/input/gamepad.h>
 | ||
|  | #include <kinc/log.h>
 | ||
|  | #include <kinc/math/core.h>
 | ||
|  | 
 | ||
|  | static void inputValueCallback(void *inContext, IOReturn inResult, void *inSender, IOHIDValueRef inIOHIDValueRef); | ||
|  | static void valueAvailableCallback(void *inContext, IOReturn inResult, void *inSender); | ||
|  | 
 | ||
|  | static void reset(struct HIDGamepad *gamepad); | ||
|  | 
 | ||
|  | static void initDeviceElements(struct HIDGamepad *gamepad, CFArrayRef elements); | ||
|  | 
 | ||
|  | static void buttonChanged(struct HIDGamepad *gamepad, IOHIDElementRef elementRef, IOHIDValueRef valueRef, int buttonIndex); | ||
|  | static void axisChanged(struct HIDGamepad *gamepad, IOHIDElementRef elementRef, IOHIDValueRef valueRef, int axisIndex); | ||
|  | 
 | ||
|  | static bool debugButtonInput = false; | ||
|  | 
 | ||
|  | static void logButton(int buttonIndex, bool pressed) { | ||
|  | 	switch (buttonIndex) { | ||
|  | 	case 0: | ||
|  | 		kinc_log(KINC_LOG_LEVEL_INFO, "A Pressed %i", pressed); | ||
|  | 		break; | ||
|  | 
 | ||
|  | 	case 1: | ||
|  | 		kinc_log(KINC_LOG_LEVEL_INFO, "B Pressed %i", pressed); | ||
|  | 		break; | ||
|  | 
 | ||
|  | 	case 2: | ||
|  | 		kinc_log(KINC_LOG_LEVEL_INFO, "X Pressed %i", pressed); | ||
|  | 		break; | ||
|  | 
 | ||
|  | 	case 3: | ||
|  | 		kinc_log(KINC_LOG_LEVEL_INFO, "Y Pressed %i", pressed); | ||
|  | 		break; | ||
|  | 
 | ||
|  | 	case 4: | ||
|  | 		kinc_log(KINC_LOG_LEVEL_INFO, "Lb Pressed %i", pressed); | ||
|  | 		break; | ||
|  | 
 | ||
|  | 	case 5: | ||
|  | 		kinc_log(KINC_LOG_LEVEL_INFO, "Rb Pressed %i", pressed); | ||
|  | 		break; | ||
|  | 
 | ||
|  | 	case 6: | ||
|  | 		kinc_log(KINC_LOG_LEVEL_INFO, "Left Stick Pressed %i", pressed); | ||
|  | 		break; | ||
|  | 
 | ||
|  | 	case 7: | ||
|  | 		kinc_log(KINC_LOG_LEVEL_INFO, "Right Stick Pressed %i", pressed); | ||
|  | 		break; | ||
|  | 
 | ||
|  | 	case 8: | ||
|  | 		kinc_log(KINC_LOG_LEVEL_INFO, "Start Pressed %i", pressed); | ||
|  | 		break; | ||
|  | 
 | ||
|  | 	case 9: | ||
|  | 		kinc_log(KINC_LOG_LEVEL_INFO, "Back Pressed %i", pressed); | ||
|  | 		break; | ||
|  | 
 | ||
|  | 	case 10: | ||
|  | 		kinc_log(KINC_LOG_LEVEL_INFO, "Home Pressed %i", pressed); | ||
|  | 		break; | ||
|  | 
 | ||
|  | 	case 11: | ||
|  | 		kinc_log(KINC_LOG_LEVEL_INFO, "Up Pressed %i", pressed); | ||
|  | 		break; | ||
|  | 
 | ||
|  | 	case 12: | ||
|  | 		kinc_log(KINC_LOG_LEVEL_INFO, "Down Pressed %i", pressed); | ||
|  | 		break; | ||
|  | 
 | ||
|  | 	case 13: | ||
|  | 		kinc_log(KINC_LOG_LEVEL_INFO, "Left Pressed %i", pressed); | ||
|  | 		break; | ||
|  | 
 | ||
|  | 	case 14: | ||
|  | 		kinc_log(KINC_LOG_LEVEL_INFO, "Right Pressed %i", pressed); | ||
|  | 		break; | ||
|  | 
 | ||
|  | 	default: | ||
|  | 		break; | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | static bool debugAxisInput = false; | ||
|  | 
 | ||
|  | static void logAxis(int axisIndex) { | ||
|  | 	switch (axisIndex) { | ||
|  | 	case 0: | ||
|  | 		kinc_log(KINC_LOG_LEVEL_INFO, "Left stick X"); | ||
|  | 		break; | ||
|  | 
 | ||
|  | 	case 1: | ||
|  | 		kinc_log(KINC_LOG_LEVEL_INFO, "Left stick Y"); | ||
|  | 		break; | ||
|  | 
 | ||
|  | 	case 2: | ||
|  | 		kinc_log(KINC_LOG_LEVEL_INFO, "Right stick X"); | ||
|  | 		break; | ||
|  | 
 | ||
|  | 	case 3: | ||
|  | 		kinc_log(KINC_LOG_LEVEL_INFO, "Right stick Y"); | ||
|  | 		break; | ||
|  | 
 | ||
|  | 	case 4: | ||
|  | 		kinc_log(KINC_LOG_LEVEL_INFO, "Left trigger"); | ||
|  | 		break; | ||
|  | 
 | ||
|  | 	case 5: | ||
|  | 		kinc_log(KINC_LOG_LEVEL_INFO, "Right trigger"); | ||
|  | 		break; | ||
|  | 
 | ||
|  | 	default: | ||
|  | 		break; | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | // Helper function to copy a CFStringRef to a cstring buffer.
 | ||
|  | // CFStringRef is converted to UTF8 and as many characters as possible are
 | ||
|  | // placed into the buffer followed by a null terminator.
 | ||
|  | // The buffer is set to an empty string if the conversion fails.
 | ||
|  | static void cstringFromCFStringRef(CFStringRef string, char *cstr, size_t clen) { | ||
|  | 	cstr[0] = '\0'; | ||
|  | 	if (string != NULL) { | ||
|  | 		char temp[256]; | ||
|  | 		if (CFStringGetCString(string, temp, 256, kCFStringEncodingUTF8)) { | ||
|  | 			temp[kinc_mini(255, (int)(clen - 1))] = '\0'; | ||
|  | 			strncpy(cstr, temp, clen); | ||
|  | 		} | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | void HIDGamepad_init(struct HIDGamepad *gamepad) { | ||
|  | 	reset(gamepad); | ||
|  | } | ||
|  | 
 | ||
|  | void HIDGamepad_destroy(struct HIDGamepad *gamepad) { | ||
|  | 	HIDGamepad_unbind(gamepad); | ||
|  | } | ||
|  | 
 | ||
|  | void HIDGamepad_bind(struct HIDGamepad *gamepad, IOHIDDeviceRef inDeviceRef, int inPadIndex) { | ||
|  | 	kinc_affirm(inDeviceRef != NULL); | ||
|  | 	kinc_affirm(inPadIndex >= 0); | ||
|  | 	kinc_affirm(gamepad->hidDeviceRef == NULL); | ||
|  | 	kinc_affirm(gamepad->hidQueueRef == NULL); | ||
|  | 	kinc_affirm(gamepad->padIndex == -1); | ||
|  | 
 | ||
|  | 	// Set device and device index
 | ||
|  | 	gamepad->hidDeviceRef = inDeviceRef; | ||
|  | 	gamepad->padIndex = inPadIndex; | ||
|  | 
 | ||
|  | 	// Initialise HID Device
 | ||
|  | 	// ...open device
 | ||
|  | 	IOHIDDeviceOpen(gamepad->hidDeviceRef, kIOHIDOptionsTypeSeizeDevice); | ||
|  | 
 | ||
|  | 	// ..register callbacks
 | ||
|  | 	IOHIDDeviceRegisterInputValueCallback(gamepad->hidDeviceRef, inputValueCallback, gamepad); | ||
|  | 	IOHIDDeviceScheduleWithRunLoop(gamepad->hidDeviceRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); | ||
|  | 
 | ||
|  | 	// ...create a queue to access element values
 | ||
|  | 	gamepad->hidQueueRef = IOHIDQueueCreate(kCFAllocatorDefault, gamepad->hidDeviceRef, 32, kIOHIDOptionsTypeNone); | ||
|  | 	if (CFGetTypeID(gamepad->hidQueueRef) == IOHIDQueueGetTypeID()) { | ||
|  | 		IOHIDQueueStart(gamepad->hidQueueRef); | ||
|  | 		IOHIDQueueRegisterValueAvailableCallback(gamepad->hidQueueRef, valueAvailableCallback, gamepad); | ||
|  | 		IOHIDQueueScheduleWithRunLoop(gamepad->hidQueueRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	// ...get all elements (buttons, axes)
 | ||
|  | 	CFArrayRef elementCFArrayRef = IOHIDDeviceCopyMatchingElements(gamepad->hidDeviceRef, NULL, kIOHIDOptionsTypeNone); | ||
|  | 	initDeviceElements(gamepad, elementCFArrayRef); | ||
|  | 
 | ||
|  | 	// ...get device manufacturer and product details
 | ||
|  | 	{ | ||
|  | 		CFNumberRef vendorIdRef = (CFNumberRef)IOHIDDeviceGetProperty(gamepad->hidDeviceRef, CFSTR(kIOHIDVendorIDKey)); | ||
|  | 		CFNumberGetValue(vendorIdRef, kCFNumberIntType, &gamepad->hidDeviceVendorID); | ||
|  | 
 | ||
|  | 		CFNumberRef productIdRef = (CFNumberRef)IOHIDDeviceGetProperty(gamepad->hidDeviceRef, CFSTR(kIOHIDProductIDKey)); | ||
|  | 		CFNumberGetValue(productIdRef, kCFNumberIntType, &gamepad->hidDeviceProductID); | ||
|  | 
 | ||
|  | 		CFStringRef vendorRef = (CFStringRef)IOHIDDeviceGetProperty(gamepad->hidDeviceRef, CFSTR(kIOHIDManufacturerKey)); | ||
|  | 		cstringFromCFStringRef(vendorRef, gamepad->hidDeviceVendor, sizeof(gamepad->hidDeviceVendor)); | ||
|  | 
 | ||
|  | 		CFStringRef productRef = (CFStringRef)IOHIDDeviceGetProperty(gamepad->hidDeviceRef, CFSTR(kIOHIDProductKey)); | ||
|  | 		cstringFromCFStringRef(productRef, gamepad->hidDeviceProduct, sizeof(gamepad->hidDeviceProduct)); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	// Initialise Kore::Gamepad for this HID Device
 | ||
|  | 	//**
 | ||
|  | 	/*Gamepad *gamepad = Gamepad::get(padIndex);
 | ||
|  | 	gamepad->vendor 	 = hidDeviceVendor; | ||
|  | 	gamepad->productName = hidDeviceProduct;*/ | ||
|  | 
 | ||
|  | 	kinc_log(KINC_LOG_LEVEL_INFO, "HIDGamepad.bind: <%p> idx:%d [0x%x:0x%x] [%s] [%s]", inDeviceRef, gamepad->padIndex, gamepad->hidDeviceVendorID, | ||
|  | 	         gamepad->hidDeviceProductID, gamepad->hidDeviceVendor, gamepad->hidDeviceProduct); | ||
|  | } | ||
|  | 
 | ||
|  | static void initDeviceElements(struct HIDGamepad *gamepad, CFArrayRef elements) { | ||
|  | 	kinc_affirm(elements != NULL); | ||
|  | 
 | ||
|  | 	for (CFIndex i = 0, count = CFArrayGetCount(elements); i < count; ++i) { | ||
|  | 		IOHIDElementRef elementRef = (IOHIDElementRef)CFArrayGetValueAtIndex(elements, i); | ||
|  | 		IOHIDElementType elemType = IOHIDElementGetType(elementRef); | ||
|  | 
 | ||
|  | 		IOHIDElementCookie cookie = IOHIDElementGetCookie(elementRef); | ||
|  | 
 | ||
|  | 		uint32_t usagePage = IOHIDElementGetUsagePage(elementRef); | ||
|  | 		uint32_t usage = IOHIDElementGetUsage(elementRef); | ||
|  | 
 | ||
|  | 		// Match up items
 | ||
|  | 		switch (usagePage) { | ||
|  | 		case kHIDPage_GenericDesktop: | ||
|  | 			switch (usage) { | ||
|  | 			case kHIDUsage_GD_X: // Left stick X
 | ||
|  | 				// log(Info, "Left stick X axis[0] = %i", cookie);
 | ||
|  | 				gamepad->axis[0] = cookie; | ||
|  | 				break; | ||
|  | 			case kHIDUsage_GD_Y: // Left stick Y
 | ||
|  | 				// log(Info, "Left stick Y axis[1] = %i", cookie);
 | ||
|  | 				gamepad->axis[1] = cookie; | ||
|  | 				break; | ||
|  | 			case kHIDUsage_GD_Z: // Left trigger
 | ||
|  | 				// log(Info, "Left trigger axis[4] = %i", cookie);
 | ||
|  | 				gamepad->axis[4] = cookie; | ||
|  | 				break; | ||
|  | 			case kHIDUsage_GD_Rx: // Right stick X
 | ||
|  | 				// log(Info, "Right stick X axis[2] = %i", cookie);
 | ||
|  | 				gamepad->axis[2] = cookie; | ||
|  | 				break; | ||
|  | 			case kHIDUsage_GD_Ry: // Right stick Y
 | ||
|  | 				// log(Info, "Right stick Y axis[3] = %i", cookie);
 | ||
|  | 				gamepad->axis[3] = cookie; | ||
|  | 				break; | ||
|  | 			case kHIDUsage_GD_Rz: // Right trigger
 | ||
|  | 				// log(Info, "Right trigger axis[5] = %i", cookie);
 | ||
|  | 				gamepad->axis[5] = cookie; | ||
|  | 				break; | ||
|  | 			case kHIDUsage_GD_Hatswitch: | ||
|  | 				break; | ||
|  | 			default: | ||
|  | 				break; | ||
|  | 			} | ||
|  | 			break; | ||
|  | 		case kHIDPage_Button: | ||
|  | 			if ((usage >= 1) && (usage <= 15)) { | ||
|  | 				// Button 1-11
 | ||
|  | 				gamepad->buttons[usage - 1] = cookie; | ||
|  | 				// log(Info, "Button %i = %i", usage-1, cookie);
 | ||
|  | 			} | ||
|  | 			break; | ||
|  | 		default: | ||
|  | 			break; | ||
|  | 		} | ||
|  | 
 | ||
|  | 		if (elemType == kIOHIDElementTypeInput_Misc || elemType == kIOHIDElementTypeInput_Button || elemType == kIOHIDElementTypeInput_Axis) { | ||
|  | 			if (!IOHIDQueueContainsElement(gamepad->hidQueueRef, elementRef)) | ||
|  | 				IOHIDQueueAddElement(gamepad->hidQueueRef, elementRef); | ||
|  | 		} | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | void HIDGamepad_unbind(struct HIDGamepad *gamepad) { | ||
|  | 	kinc_log(KINC_LOG_LEVEL_INFO, "HIDGamepad.unbind: idx:%d [0x%x:0x%x] [%s] [%s]", gamepad->padIndex, gamepad->hidDeviceVendorID, gamepad->hidDeviceProductID, | ||
|  | 	         gamepad->hidDeviceVendor, gamepad->hidDeviceProduct); | ||
|  | 
 | ||
|  | 	if (gamepad->hidQueueRef) { | ||
|  | 		IOHIDQueueStop(gamepad->hidQueueRef); | ||
|  | 		IOHIDQueueUnscheduleFromRunLoop(gamepad->hidQueueRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if (gamepad->hidDeviceRef) { | ||
|  | 		IOHIDDeviceUnscheduleFromRunLoop(gamepad->hidDeviceRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); | ||
|  | 		IOHIDDeviceClose(gamepad->hidDeviceRef, kIOHIDOptionsTypeSeizeDevice); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if (gamepad->padIndex >= 0) { | ||
|  | 		//**
 | ||
|  | 		/*Gamepad *gamepad = Gamepad::get(padIndex);
 | ||
|  | 		gamepad->vendor 	 = nullptr; | ||
|  | 		gamepad->productName = nullptr;*/ | ||
|  | 	} | ||
|  | 
 | ||
|  | 	reset(gamepad); | ||
|  | } | ||
|  | 
 | ||
|  | static void reset(struct HIDGamepad *gamepad) { | ||
|  | 	gamepad->padIndex = -1; | ||
|  | 	gamepad->hidDeviceRef = NULL; | ||
|  | 	gamepad->hidQueueRef = NULL; | ||
|  | 	gamepad->hidDeviceVendor[0] = '\0'; | ||
|  | 	gamepad->hidDeviceProduct[0] = '\0'; | ||
|  | 	gamepad->hidDeviceVendorID = 0; | ||
|  | 	gamepad->hidDeviceProductID = 0; | ||
|  | 
 | ||
|  | 	memset(gamepad->axis, 0, sizeof(gamepad->axis)); | ||
|  | 	memset(gamepad->buttons, 0, sizeof(gamepad->buttons)); | ||
|  | } | ||
|  | 
 | ||
|  | static void buttonChanged(struct HIDGamepad *gamepad, IOHIDElementRef elementRef, IOHIDValueRef valueRef, int buttonIndex) { | ||
|  | 	// double rawValue = IOHIDValueGetIntegerValue(valueRef);
 | ||
|  | 	double rawValue = IOHIDValueGetScaledValue(valueRef, kIOHIDValueScaleTypePhysical); | ||
|  | 
 | ||
|  | 	// Normalize button value to the range [0.0, 1.0] (0 - release, 1 - pressed)
 | ||
|  | 	double min = IOHIDElementGetLogicalMin(elementRef); | ||
|  | 	double max = IOHIDElementGetLogicalMax(elementRef); | ||
|  | 	double normalize = (rawValue - min) / (max - min); | ||
|  | 
 | ||
|  | 	// log(Info, "%f %f %f %f", rawValue, min, max, normalize);
 | ||
|  | 
 | ||
|  | 	kinc_internal_gamepad_trigger_button(gamepad->padIndex, buttonIndex, normalize); | ||
|  | 
 | ||
|  | 	if (debugButtonInput) | ||
|  | 		logButton(buttonIndex, (normalize != 0)); | ||
|  | } | ||
|  | 
 | ||
|  | static void axisChanged(struct HIDGamepad *gamepad, IOHIDElementRef elementRef, IOHIDValueRef valueRef, int axisIndex) { | ||
|  | 	// double rawValue = IOHIDValueGetIntegerValue(valueRef);
 | ||
|  | 	double rawValue = IOHIDValueGetScaledValue(valueRef, kIOHIDValueScaleTypePhysical); | ||
|  | 
 | ||
|  | 	// Normalize axis value to the range [-1.0, 1.0] (e.g. -1 - left, 0 - release, 1 - right)
 | ||
|  | 	double min = IOHIDElementGetPhysicalMin(elementRef); | ||
|  | 	double max = IOHIDElementGetPhysicalMax(elementRef); | ||
|  | 	double normalize = normalize = (((rawValue - min) / (max - min)) * 2) - 1; | ||
|  | 
 | ||
|  | 	// Invert Y axis
 | ||
|  | 	if (axisIndex % 2 == 1) | ||
|  | 		normalize = -normalize; | ||
|  | 
 | ||
|  | 	// log(Info, "%f %f %f %f", rawValue, min, max, normalize);
 | ||
|  | 
 | ||
|  | 	kinc_internal_gamepad_trigger_axis(gamepad->padIndex, axisIndex, normalize); | ||
|  | 
 | ||
|  | 	if (debugAxisInput) | ||
|  | 		logAxis(axisIndex); | ||
|  | } | ||
|  | 
 | ||
|  | static void inputValueCallback(void *inContext, IOReturn inResult, void *inSender, IOHIDValueRef inIOHIDValueRef) {} | ||
|  | 
 | ||
|  | static void valueAvailableCallback(void *inContext, IOReturn inResult, void *inSender) { | ||
|  | 	struct HIDGamepad *pad = (struct HIDGamepad *)inContext; | ||
|  | 	do { | ||
|  | 		IOHIDValueRef valueRef = IOHIDQueueCopyNextValueWithTimeout((IOHIDQueueRef)inSender, 0.); | ||
|  | 		if (!valueRef) | ||
|  | 			break; | ||
|  | 
 | ||
|  | 		// process the HID value reference
 | ||
|  | 		IOHIDElementRef elementRef = IOHIDValueGetElement(valueRef); | ||
|  | 		// IOHIDElementType elemType = IOHIDElementGetType(elementRef);
 | ||
|  | 		// log(Info, "Type %d %d\n", elemType, elementRef);
 | ||
|  | 
 | ||
|  | 		IOHIDElementCookie cookie = IOHIDElementGetCookie(elementRef); | ||
|  | 		// uint32_t page = IOHIDElementGetUsagePage(elementRef);
 | ||
|  | 		// uint32_t usage = IOHIDElementGetUsage(elementRef);
 | ||
|  | 		// log(Info, "page %i, usage %i cookie %i", page, usage, cookie);
 | ||
|  | 
 | ||
|  | 		// Check button
 | ||
|  | 		for (int i = 0, c = sizeof(pad->buttons); i < c; ++i) { | ||
|  | 			if (cookie == pad->buttons[i]) { | ||
|  | 				buttonChanged(pad, elementRef, valueRef, i); | ||
|  | 				break; | ||
|  | 			} | ||
|  | 		} | ||
|  | 
 | ||
|  | 		// Check axes
 | ||
|  | 		for (int i = 0, c = sizeof(pad->axis); i < c; ++i) { | ||
|  | 			if (cookie == pad->axis[i]) { | ||
|  | 				axisChanged(pad, elementRef, valueRef, i); | ||
|  | 				break; | ||
|  | 			} | ||
|  | 		} | ||
|  | 
 | ||
|  | 		CFRelease(valueRef); | ||
|  | 	} while (1); | ||
|  | } | ||
|  | 
 | ||
|  | const char *kinc_gamepad_vendor(int gamepad) { | ||
|  | 	return "unknown"; | ||
|  | } | ||
|  | 
 | ||
|  | const char *kinc_gamepad_product_name(int gamepad) { | ||
|  | 	return "unknown"; | ||
|  | } |