// NOTE: I pieced this together through trial and error, any corrections are welcome static void hid_device_input_callback(void* context, IOReturn result, void* sender, IOHIDValueRef value) { struct apple_pad_connection* connection = (struct apple_pad_connection*)context; IOHIDElementRef element = IOHIDValueGetElement(value); uint32_t type = IOHIDElementGetType(element); uint32_t page = IOHIDElementGetUsagePage(element); uint32_t use = IOHIDElementGetUsage(element); // Joystick handler: TODO: Can GamePad work the same? if (type == kIOHIDElementTypeInput_Button && page == kHIDPage_Button) { CFIndex state = IOHIDValueGetIntegerValue(value); if (state) g_current_input_data.pad_buttons[connection->slot] |= (1 << (use - 1)); else g_current_input_data.pad_buttons[connection->slot] &= ~(1 << (use - 1)); } else if (type == kIOHIDElementTypeInput_Misc && page == kHIDPage_GenericDesktop) { static const uint32_t axis_use_ids[4] = { 48, 49, 50, 53 }; for (int i = 0; i < 4; i ++) { if (use == axis_use_ids[i]) { CFIndex min = IOHIDElementGetPhysicalMin(element); CFIndex max = IOHIDElementGetPhysicalMax(element) - min; CFIndex state = IOHIDValueGetIntegerValue(value) - min; float val = (float)state / (float)max; g_current_input_data.pad_axis[connection->slot][i] = ((val * 2.0f) - 1.0f) * 32767.0f; } } } }
int joy_hidlib_enumerate_elements(joy_hid_device_t *device) { IOHIDDeviceRef dev = device->internal_device; if(dev == NULL) { return -1; } /* get all elements of device */ CFArrayRef internal_elements = IOHIDDeviceCopyMatchingElements( dev, NULL, 0 ); if(!internal_elements) { return -1; } /* get number of elements */ CFIndex cnt = CFArrayGetCount( internal_elements ); device->num_elements = (int)cnt; /* create elements array */ joy_hid_element_t *elements = (joy_hid_element_t *) lib_malloc(sizeof(joy_hid_element_t) * cnt); if(elements == NULL) { CFRelease(internal_elements); internal_elements = NULL; return -1; } /* enumerate and convert all elements */ CFIndex i; joy_hid_element_t *e = elements; for(i=0;i<cnt;i++) { IOHIDElementRef internal_element = ( IOHIDElementRef ) CFArrayGetValueAtIndex( internal_elements, i ); if ( internal_element ) { uint32_t usage_page = IOHIDElementGetUsagePage( internal_element ); uint32_t usage = IOHIDElementGetUsage( internal_element ); CFIndex min = IOHIDElementGetPhysicalMin( internal_element ); CFIndex max = IOHIDElementGetPhysicalMax( internal_element ); e->usage_page = (int)usage_page; e->usage = (int)usage; e->min_value = (int)min; e->max_value = (int)max; e->internal_element = internal_element; } else { e->usage_page = -1; e->usage = -1; e->min_value = -1; e->max_value = -1; e->internal_element = NULL; } e++; } /* keep the reference until the elements are free'ed again */ device->internal_elements = internal_elements; device->elements = elements; return (int)cnt; }
// ************************************************************************* // // IOHIDElement_SetupCalibration(inIOHIDElementRef) // // Purpose: set default values for the element calibration parameters // // Inputs: inIOHIDElementRef - the IOHIDElementRef for this element // // Returns: nothing // void IOHIDElement_SetupCalibration(IOHIDElementRef inIOHIDElementRef) { // these are the min/max values returned by IOHIDValueGetScaledValue(v, kIOHIDValueScaleTypeCalibrated); IOHIDElement_SetCalibrationMin(inIOHIDElementRef, IOHIDElementGetLogicalMin(inIOHIDElementRef)); IOHIDElement_SetCalibrationMax(inIOHIDElementRef, IOHIDElementGetLogicalMax(inIOHIDElementRef)); CFIndex phyMin = IOHIDElementGetPhysicalMin(inIOHIDElementRef); CFIndex phyMax = IOHIDElementGetPhysicalMax(inIOHIDElementRef); CFIndex phyRange = phyMax - phyMin; // calculate the middle physical value we would expect from this element double phyMid = (phyMin + phyMax) / 2.f; // this is the granularity of the values returned by IOHIDValueGetScaledValue(v, kIOHIDValueScaleTypeCalibrated); // for example if set to 0.1 the values returned will be multiples of 0.1 (0.1, 0.2, 0.3, etc.) IOHIDElement_SetCalibrationGranularity(inIOHIDElementRef, 0.); Boolean isRelative = IOHIDElementIsRelative(inIOHIDElementRef); Boolean hasPreferredState = IOHIDElementHasPreferredState(inIOHIDElementRef); // define the dead zone (like in the middle of joystick axis) if (!isRelative && hasPreferredState && (phyRange > 3)) { IOHIDElement_SetCalibrationDeadZoneMin(inIOHIDElementRef, phyMid - 1.0); IOHIDElement_SetCalibrationDeadZoneMax(inIOHIDElementRef, phyMid + 1.0); } else { IOHIDElement_SetCalibrationDeadZoneMin(inIOHIDElementRef, 0.); IOHIDElement_SetCalibrationDeadZoneMax(inIOHIDElementRef, 0.); } // get the current value of this element double phyValue = IOHIDElement_GetValue(inIOHIDElementRef, kIOHIDValueScaleTypePhysical); #if true // use that that value to determine the min/max saturation values used for calibration IOHIDElement_SetCalibrationSaturationMin(inIOHIDElementRef, phyValue); IOHIDElement_SetCalibrationSaturationMax(inIOHIDElementRef, phyValue); #else // if 1 #if true // this value determines the min/max values that have been recieved from the device element IOHIDElement_SetCalibrationSaturationMin(inIOHIDElementRef, (phyMin + phyMid) / 2.f); IOHIDElement_SetCalibrationSaturationMax(inIOHIDElementRef, (phyMid + phyMax) / 2.f); #else // if 1 // use it as our min/max saturation // this value determines the min/max values that have been recieved from the device element IOHIDElement_SetCalibrationSaturationMin(inIOHIDElementRef, phyMid); IOHIDElement_SetCalibrationSaturationMax(inIOHIDElementRef, phyMid); #endif // if 1 // and the current value to adjust the current saturation values if it's outside their range if (phyValue < IOHIDElement_GetCalibrationSaturationMin(inIOHIDElementRef)) { IOHIDElement_SetCalibrationSaturationMin(inIOHIDElementRef, phyValue); } if (phyValue > IOHIDElement_GetCalibrationSaturationMax(inIOHIDElementRef)) { IOHIDElement_SetCalibrationSaturationMax(inIOHIDElementRef, phyValue); } #endif // if 1 } // IOHIDElement_SetupCalibration
//************************************************************************* // // IOHIDElement_SetupCalibration( inElementRef ) // // Purpose: set default values for the element calibration parameters // // Inputs: inElementRef - the IOHIDElementRef for this element // // Returns: nothing // void IOHIDElement_SetupCalibration( IOHIDElementRef inIOHIDElementRef ) { // these are the min/max values returned by IOHIDValueGetScaledValue( v, kIOHIDValueScaleTypeCalibrated ); IOHIDElement_SetCalibrationMin( inIOHIDElementRef, IOHIDElementGetLogicalMin( inIOHIDElementRef ) ); IOHIDElement_SetCalibrationMax( inIOHIDElementRef, IOHIDElementGetLogicalMax( inIOHIDElementRef ) ); // this is the granularity of the values returned by IOHIDValueGetScaledValue( v, kIOHIDValueScaleTypeCalibrated ); // for example if set to 0.1 the values returned will be multiples of 0.1 ( 0.1, 0.2, 0.3, etc. ) IOHIDElement_SetCalibrationGranularity( inIOHIDElementRef, 0. ); // these define the dead zone (like in the middel of joystick axis) IOHIDElement_SetCalibrationDeadZoneMin( inIOHIDElementRef, 0 ); IOHIDElement_SetCalibrationDeadZoneMax( inIOHIDElementRef, 0 ); #if 1 // get the current value of this element double value = IOHIDElement_GetValue( inIOHIDElementRef, kIOHIDValueScaleTypePhysical ); // use it as our min/mas saturation IOHIDElement_SetCalibrationSaturationMin( inIOHIDElementRef, value ); IOHIDElement_SetCalibrationSaturationMax( inIOHIDElementRef, value ); #else // calculate the middle physical value we would expect from this element CFIndex valueMin = IOHIDElementGetPhysicalMin( inIOHIDElementRef ); CFIndex valueMax = IOHIDElementGetPhysicalMax( inIOHIDElementRef ); CFIndex valueMid = ( valueMin + valueMax ) / 2; // use it as our min/mas saturation // this value determines the min/max values that have been recieved from the device element IOHIDElement_SetCalibrationSaturationMin( inIOHIDElementRef, valueMid ); IOHIDElement_SetCalibrationSaturationMax( inIOHIDElementRef, valueMid ); // get the current value of this element double value = IOHIDElement_GetValue( inIOHIDElementRef, kIOHIDValueScaleTypePhysical ); // and use it to adjust the current saturation values if it's outside their range if ( value < IOHIDElement_GetCalibrationSaturationMin( inIOHIDElementRef ) ) { IOHIDElement_SetCalibrationSaturationMin( inIOHIDElementRef, value ); } if ( value > IOHIDElement_GetCalibrationSaturationMax( inIOHIDElementRef ) ) { IOHIDElement_SetCalibrationSaturationMax( inIOHIDElementRef, value ); } #endif } // IOHIDElement_SetupCalibration
bool HIDGamepad::maybeAddAxis(IOHIDElementRef element) { uint32_t usagePage = IOHIDElementGetUsagePage(element); if (usagePage != kHIDPage_GenericDesktop) return false; uint32_t usage = IOHIDElementGetUsage(element); // This range covers the standard axis usages. if (usage < kHIDUsage_GD_X || usage > kHIDUsage_GD_Rz) return false; CFIndex min = IOHIDElementGetPhysicalMin(element); CFIndex max = IOHIDElementGetPhysicalMax(element); m_axes.append(std::make_unique<HIDGamepadAxis>(min, max, element)); IOHIDElementCookie cookie = IOHIDElementGetCookie(element); m_elementMap.set(cookie, m_axes.last().get()); return true; }
static void apple_hid_device_input_callback(void *data, IOReturn result, void* sender, IOHIDValueRef value) { driver_t *driver = driver_get_ptr(); apple_input_data_t *apple = (apple_input_data_t*)driver->input_data; struct apple_hid_adapter *adapter = (struct apple_hid_adapter*)data; IOHIDElementRef element = IOHIDValueGetElement(value); uint32_t type = IOHIDElementGetType(element); uint32_t page = IOHIDElementGetUsagePage(element); uint32_t use = IOHIDElementGetUsage(element); if (type != kIOHIDElementTypeInput_Misc) if (type != kIOHIDElementTypeInput_Button) if (type != kIOHIDElementTypeInput_Axis) return; /* Joystick handler. * TODO: Can GamePad work the same? */ switch (page) { case kHIDPage_GenericDesktop: switch (type) { case kIOHIDElementTypeInput_Misc: switch (use) { case kHIDUsage_GD_Hatswitch: break; default: { int i; static const uint32_t axis_use_ids[4] = { 48, 49, 50, 53 }; for (i = 0; i < 4; i ++) { CFIndex min = IOHIDElementGetPhysicalMin(element); CFIndex max = IOHIDElementGetPhysicalMax(element) - min; CFIndex state = IOHIDValueGetIntegerValue(value) - min; float val = (float)state / (float)max; if (use != axis_use_ids[i]) continue; apple->axes[adapter->slot][i] = ((val * 2.0f) - 1.0f) * 32767.0f; } } break; } break; } break; case kHIDPage_Button: switch (type) { case kIOHIDElementTypeInput_Button: { CFIndex state = IOHIDValueGetIntegerValue(value); unsigned id = use - 1; if (state) BIT64_SET(apple->buttons[adapter->slot], id); else BIT64_CLEAR(apple->buttons[adapter->slot], id); } break; } break; } }
// ************************************************************************* // // HIDConfigureAction(outIOHIDDeviceRef, outIOHIDElementRef, inTimeout) // // Purpose: polls all devices and elements for a change greater than kPercentMove. // Times out after given time returns 1 and pointer to device and element // if found; returns 0 and NULL for both parameters if not found // // Inputs: outIOHIDDeviceRef - address where to store the device // outIOHIDElementRef - address where to store the element // inTimeout - the timeout // Returns: Boolean - if successful // outIOHIDDeviceRef - the device // outIOHIDElementRef - the element // Boolean HIDConfigureActionOfType(actionTypeMask inActionTypeMask, double inTimeout, IOHIDDeviceRef * outIOHIDDeviceRef, IOHIDElementRef * outIOHIDElementRef) { // param error? if (!outIOHIDDeviceRef || !outIOHIDElementRef) { return (false); } if (!gDeviceCFArrayRef) { // if we do not have a device list // and we can't build another list if (!HIDBuildDeviceList(0, 0) || !gDeviceCFArrayRef) { return (false); // bail } } IOHIDDeviceRef tIOHIDDeviceRef; IOHIDElementRef tIOHIDElementRef; IOHIDElementType elementType = 0; switch (inActionTypeMask) { case kActionTypeButton: { elementType = kIOHIDElementTypeInput_Button; break; } case kActionTypeAxis: { elementType = kIOHIDElementTypeInput_Misc; break; } case kActionTypeAll: default: { elementType = 0; break; } } // switch // determine the maximum number of elements CFIndex maxElements = 0; CFIndex devIndex, devCount = CFArrayGetCount(gDeviceCFArrayRef); for (devIndex = 0; devIndex < devCount; devIndex++) { tIOHIDDeviceRef = (IOHIDDeviceRef) CFArrayGetValueAtIndex(gDeviceCFArrayRef, devIndex); if (!tIOHIDDeviceRef) { continue; // skip this one } // HIDDumpDeviceInfo(tIOHIDDeviceRef); CFIndex count = HIDCountDeviceElementsOfType(tIOHIDDeviceRef, elementType); if (count > maxElements) { maxElements = count; } } if (!(devCount * maxElements)) { return (false); } #if true // NSDictionary * matchDictionary = @{@(kIOHIDElementTypeKey): @(elementType)}; const void *keys[] = {CFSTR(kIOHIDElementTypeKey)}; const void *vals[] = {CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &elementType)}; CFDictionaryRef matchingDict = CFDictionaryCreate(kCFAllocatorDefault, keys, vals, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFRelease(vals[0]); #endif // if 1 // allocate an array of int's in which to store devCount * maxElements values double *saveValueArray = (double *) calloc(devCount * maxElements, sizeof(double)); // clear 2D array to save values // remember when we start; used to calculate timeout clock_t start = clock(), end; // on first pass store initial values / compare current values to initial values on subsequent passes Boolean found = false, first = true; while (!found) { double maxDeltaPercent = 0; // we want to find the one that moves the most // (percentage wise) for (devIndex = 0; devIndex < devCount; devIndex++) { tIOHIDDeviceRef = (IOHIDDeviceRef) CFArrayGetValueAtIndex(gDeviceCFArrayRef, devIndex); if (!tIOHIDDeviceRef) { continue; // skip this one } gElementCFArrayRef = IOHIDDeviceCopyMatchingElements(tIOHIDDeviceRef, matchingDict, kIOHIDOptionsTypeNone); if (gElementCFArrayRef) { CFIndex eleIndex, eleCount = CFArrayGetCount(gElementCFArrayRef); for (eleIndex = 0; eleIndex < eleCount; eleIndex++) { tIOHIDElementRef = (IOHIDElementRef) CFArrayGetValueAtIndex(gElementCFArrayRef, eleIndex); if (!tIOHIDElementRef) { continue; } IOHIDElementType tIOHIDElementType = IOHIDElementGetType(tIOHIDElementRef); // only care about inputs (no outputs or features) if (tIOHIDElementType <= kIOHIDElementTypeInput_ScanCodes) { if (IOHIDElementIsArray(tIOHIDElementRef)) { // printf("ARRAY!\n"); continue; // skip array elements } if (elementType && ((tIOHIDElementType != elementType))) { continue; } uint32_t usagePage = IOHIDElementGetUsagePage(tIOHIDElementRef); uint32_t usage = IOHIDElementGetUsage(tIOHIDElementRef); uint32_t reportCount = IOHIDElementGetReportCount(tIOHIDElementRef); #ifdef DEBUG if (first) { HIDDumpElementInfo(tIOHIDElementRef); fflush(stdout); uint32_t vendorID = IOHIDDevice_GetVendorID(tIOHIDDeviceRef); uint32_t productID = IOHIDDevice_GetProductID(tIOHIDDeviceRef); IOHIDElementCookie cookie = IOHIDElementGetCookie(tIOHIDElementRef); if ((0x054C == vendorID) && (0x0268 == productID) && (0x001E == (uint32_t) cookie)) { // printf("DING!\n"); } } #endif // ifdef DEBUG #if true // work-around for IOHIDValueGetScaledValue crash // (when element report count > 1) if (reportCount > 1) { // printf("REPORT!\n"); continue; // skip reports } #endif // if 1 // ignore PID elements and arrays if ((kHIDPage_PID != usagePage) && ((-1) != usage)) { // get this elements current value double value = 0.0; // default value is zero IOHIDValueRef tIOHIDValueRef; IOReturn ioReturn = IOHIDDeviceGetValue(tIOHIDDeviceRef, tIOHIDElementRef, &tIOHIDValueRef); if (kIOReturnSuccess == ioReturn) { value = IOHIDValueGetScaledValue(tIOHIDValueRef, kIOHIDValueScaleTypePhysical); } if (first) { saveValueArray[(devIndex * maxElements) + eleIndex] = value; } else { double initialValue = saveValueArray[(devIndex * maxElements) + eleIndex]; CFIndex valueMin = IOHIDElementGetPhysicalMin(tIOHIDElementRef); CFIndex valueMax = IOHIDElementGetPhysicalMax(tIOHIDElementRef); double deltaPercent = fabs((initialValue - value) * 100.0 / (valueMax - valueMin)); #if false // debug code useful to dump out value info for // specific (vendorID, productID, usagePage and // usage) device if (!first) { // Device: 0x13b6a0 = { Logitech Inc. - WingMan Force 3D, vendorID: 0x046D, productID: 0xC283, // usage: 0x0001:0x0004, "Generic Desktop Joystick" if ((vendorID == 0x046D) && (productID == 0xC283)) { if ((kHIDPage_GenericDesktop == usagePage) && (kHIDUsage_GD_Rz == usage)) { printf("initial: %6.2f, value: %6.2f, diff: %6.2f, delta percent: %6.2f!\n", initialValue, value, fabs(initialValue - value), deltaPercent); } } } deltaPercent = 0.0; #endif // if false if (deltaPercent >= kPercentMove) { found = true; if (deltaPercent > maxDeltaPercent) { maxDeltaPercent = deltaPercent; *outIOHIDDeviceRef = tIOHIDDeviceRef; *outIOHIDElementRef = tIOHIDElementRef; } break; } } // if first } // if usage } // if type } // for elements... CFRelease(gElementCFArrayRef); gElementCFArrayRef = NULL; } // if (gElementCFArrayRef) if (found) { // HIDDumpElementInfo(tIOHIDElementRef); break; // DONE! } } // for devices first = false; // no longer the first pass // are we done? end = clock(); double secs = (double) (end - start) / CLOCKS_PER_SEC; if (secs > inTimeout) { break; // (yes) timeout } } // while (!found) if (saveValueArray) { free(saveValueArray); } // return device and element moved if (!found) { *outIOHIDDeviceRef = NULL; *outIOHIDElementRef = NULL; } CFRelease(matchingDict); return (found); } // HIDConfigureAction
JoystickState JoystickImpl::update() { static const JoystickState disconnectedState; // return this if joystick was disconnected JoystickState state; // otherwise return that state.connected = true; // Note: free up is done in close() which is called, if required, // by the joystick manager. So we don't release buttons nor axes here. // First, let's determine if the joystick is still connected Location selfLoc = m_locationIDs[m_index]; // Get all devices CFSetRef devices = HIDJoystickManager::getInstance().copyJoysticks(); if (devices == NULL) return disconnectedState; // Get a usable copy of the joysticks devices. CFIndex joysticksCount = CFSetGetCount(devices); CFTypeRef devicesArray[joysticksCount]; CFSetGetValues(devices, devicesArray); // Search for it bool found = false; for (CFIndex i(0); i < joysticksCount; ++i) { IOHIDDeviceRef d = (IOHIDDeviceRef)devicesArray[i]; if (selfLoc == HIDInputManager::getLocationID(d)) { found = true; break; // Stop looping } } // Release unused stuff CFRelease(devices); // If not found we consider it disconnected if (!found) return disconnectedState; // Update buttons' state unsigned int i = 0; for (ButtonsVector::iterator it(m_buttons.begin()); it != m_buttons.end(); ++it, ++i) { IOHIDValueRef value = 0; IOHIDDeviceGetValue(IOHIDElementGetDevice(*it), *it, &value); // Check for plug out. if (!value) { // No value? Hum... Seems like the joystick is gone return disconnectedState; } // 1 means pressed, others mean released state.buttons[i] = IOHIDValueGetIntegerValue(value) == 1; } // Update axes' state for (AxisMap::iterator it = m_axis.begin(); it != m_axis.end(); ++it) { IOHIDValueRef value = 0; IOHIDDeviceGetValue(IOHIDElementGetDevice(it->second), it->second, &value); // Check for plug out. if (!value) { // No value? Hum... Seems like the joystick is gone return disconnectedState; } // We want to bind [physicalMin,physicalMax] to [-100=min,100=max]. // // General formula to bind [a,b] to [c,d] with a linear progression: // // f: [a, b] -> [c, d] // x |-> (x-a)(d-c)/(b-a)+c // // This method might not be very accurate (the "0 position" can be // slightly shift with some device) but we don't care because most // of devices are so sensitive that this is not relevant. double physicalMax = IOHIDElementGetPhysicalMax(it->second); double physicalMin = IOHIDElementGetPhysicalMin(it->second); double scaledMin = -100; double scaledMax = 100; double physicalValue = IOHIDValueGetScaledValue(value, kIOHIDValueScaleTypePhysical); float scaledValue = (((physicalValue - physicalMin) * (scaledMax - scaledMin)) / (physicalMax - physicalMin)) + scaledMin; state.axes[it->first] = scaledValue; } return state; }
Boolean HIDConfigureAction( IOHIDDeviceRef* outIOHIDDeviceRef, IOHIDElementRef *outIOHIDElementRef, float inTimeout ) { // param error? if ( !outIOHIDDeviceRef || !outIOHIDElementRef ) { return 0; } if ( !gDeviceCFArrayRef ) { // if we do not have a device list // and we can't build another list if ( !HIDBuildDeviceList( 0, 0 ) || !gDeviceCFArrayRef ) { return FALSE; // bail } } IOHIDDeviceRef tIOHIDDeviceRef; IOHIDElementRef tIOHIDElementRef; // remember when we start; used to calculate timeout clock_t start = clock(), end; // determine the maximum number of elements CFIndex maxElements = 0; CFIndex devIndex, devCount = CFArrayGetCount( gDeviceCFArrayRef ); for ( devIndex = 0; devIndex < devCount; devIndex++ ) { tIOHIDDeviceRef = ( IOHIDDeviceRef ) CFArrayGetValueAtIndex( gDeviceCFArrayRef, devIndex ); if ( !tIOHIDDeviceRef ) { continue; // skip this one } UInt32 count = HIDCountDeviceElements( tIOHIDDeviceRef, kHIDElementTypeInput ); if ( count > maxElements ) { maxElements = count; } } // allocate an array of int's in which to store devCount * maxElements values double* saveValueArray = ( double * ) calloc( devCount * maxElements, sizeof( double ) ); // clear 2D array to save values // on first pass store initial values / compare current values to initial values on subsequent passes Boolean found = FALSE, first = TRUE; while ( !found ) { double maxDeltaPercent = 0; // we want to find the one that moves the most ( percentage wise ) for ( devIndex = 0; devIndex < devCount; devIndex++ ) { IOHIDDeviceRef tIOHIDDeviceRef = ( IOHIDDeviceRef ) CFArrayGetValueAtIndex( gDeviceCFArrayRef, devIndex ); if ( !tIOHIDDeviceRef ) { continue; // skip this one } #ifdef DEBUG long vendorID = IOHIDDevice_GetVendorID( tIOHIDDeviceRef ); long productID = IOHIDDevice_GetProductID( tIOHIDDeviceRef ); #endif gElementCFArrayRef = IOHIDDeviceCopyMatchingElements( tIOHIDDeviceRef, NULL, kIOHIDOptionsTypeNone ); if ( gElementCFArrayRef ) { CFIndex eleIndex, eleCount = CFArrayGetCount( gElementCFArrayRef ); for ( eleIndex = 0; eleIndex < eleCount; eleIndex++ ) { tIOHIDElementRef = ( IOHIDElementRef ) CFArrayGetValueAtIndex( gElementCFArrayRef, eleIndex ); if ( !tIOHIDElementRef ) { continue; } IOHIDElementType tIOHIDElementType = IOHIDElementGetType( tIOHIDElementRef ); // only care about inputs (no outputs or features) if ( tIOHIDElementType <= kIOHIDElementTypeInput_ScanCodes ) { if ( IOHIDElementIsArray( tIOHIDElementRef ) ) { //printf( "ARRAY!\n" ); continue; // skip array elements } uint32_t usagePage = IOHIDElementGetUsagePage( tIOHIDElementRef ); uint32_t usage = IOHIDElementGetUsage( tIOHIDElementRef ); uint32_t reportCount = IOHIDElementGetReportCount( tIOHIDElementRef ); #ifdef DEBUG if ( first ) { IOHIDElementCookie cookie = IOHIDElementGetCookie( tIOHIDElementRef ); printf( "%s, dev: {ref:%p, ven: 0x%08lX, pro: 0x%08lX}, ele: {ref:%p, cookie: %p, usage:%04lX:%08lX}\n", __PRETTY_FUNCTION__, tIOHIDDeviceRef, vendorID, productID, tIOHIDElementRef, cookie, (long unsigned int) usagePage, (long unsigned int) usage ); fflush( stdout ); if ( ( 0x054C == vendorID ) && ( 0x0268 == productID ) && ( 0x001E == (UInt32) cookie ) ) { //printf( "DING!\n" ); } } #endif #if 1 // work-around for IOHIDValueGetScaledValue crash (when element report count > 1) if ( reportCount > 1 ) { //printf( "REPORT!\n" ); continue; // skip reports } #endif // ignore PID elements and arrays if ( ( kHIDPage_PID != usagePage ) && ( -1 != usage ) ) { // get this elements current value double value = 0.0; // default value is zero IOHIDValueRef tIOHIDValueRef; IOReturn ioReturn = IOHIDDeviceGetValue( tIOHIDDeviceRef, tIOHIDElementRef, &tIOHIDValueRef ); if ( kIOReturnSuccess == ioReturn ) { value = IOHIDValueGetScaledValue( tIOHIDValueRef, kIOHIDValueScaleTypePhysical ); } if ( first ) { saveValueArray[( devIndex * maxElements ) + eleIndex] = value; } else { double initialValue = saveValueArray[( devIndex * maxElements ) + eleIndex]; CFIndex valueMin = IOHIDElementGetPhysicalMin( tIOHIDElementRef ); CFIndex valueMax = IOHIDElementGetPhysicalMax( tIOHIDElementRef ); double deltaPercent = fabs( ( initialValue - value ) * 100.0 / (valueMax - valueMin) ); #if 0 if ( !first ) { // Device: 0x13b6a0 = { Logitech Inc. - WingMan Force 3D, vendorID: 0x046D, productID: 0xC283, usage: 0x0001:0x0004, "Generic Desktop Joystick" if ( ( vendorID == 0x046D ) && ( productID == 0xC283 ) ) { if ( ( kHIDPage_GenericDesktop == usagePage ) && ( kHIDUsage_GD_Rz == usage ) ) { printf( "initial: %6.2f, value: %6.2f, diff: %6.2f, delta percent: %6.2f!\n", initialValue, value, fabs( initialValue - value ), deltaPercent ); } } } deltaPercent = 0.0; #endif if ( deltaPercent >= kPercentMove ) { found = TRUE; if ( deltaPercent > maxDeltaPercent ) { maxDeltaPercent = deltaPercent; *outIOHIDDeviceRef = tIOHIDDeviceRef; *outIOHIDElementRef = tIOHIDElementRef; } break; } } // if first } // if usage } // if type } // for elements... CFRelease( gElementCFArrayRef ); gElementCFArrayRef = NULL; } // if ( gElementCFArrayRef ) if ( found ) { // HIDDumpElementInfo( tIOHIDElementRef ); break; // DONE! } } // for devices first = FALSE; // no longer the first pass // are we done? end = clock(); double secs = (double)( end - start ) / CLOCKS_PER_SEC; if ( secs > inTimeout ) { break; // ( yes ) timeout } } // while ( !found ) // return device and element moved if ( !found ) { *outIOHIDDeviceRef = NULL; *outIOHIDElementRef = NULL; } return found; } // HIDConfigureAction
// utility routine to dump element info void HIDDumpElementInfo(IOHIDElementRef inIOHIDElementRef) { if (inIOHIDElementRef) { printf(" Element: %p = { ", inIOHIDElementRef); #if false IOHIDDeviceRef tIOHIDDeviceRef = IOHIDElementGetDevice(inIOHIDElementRef); printf("Device: %p, ", tIOHIDDeviceRef); #endif // if 0 IOHIDElementRef parentIOHIDElementRef = IOHIDElementGetParent(inIOHIDElementRef); printf("parent: %p, ", parentIOHIDElementRef); #if false CFArrayRef childrenCFArrayRef = IOHIDElementGetChildren(inIOHIDElementRef); printf("children: %p: { ", childrenCFArrayRef); fflush(stdout); CFShow(childrenCFArrayRef); fflush(stdout); printf(" }, "); #endif // if 0 IOHIDElementCookie tIOHIDElementCookie = IOHIDElementGetCookie(inIOHIDElementRef); printf("cookie: 0x%08lX, ", (long unsigned int) tIOHIDElementCookie); IOHIDElementType tIOHIDElementType = IOHIDElementGetType(inIOHIDElementRef); switch (tIOHIDElementType) { case kIOHIDElementTypeInput_Misc: { printf("type: Misc, "); break; } case kIOHIDElementTypeInput_Button: { printf("type: Button, "); break; } case kIOHIDElementTypeInput_Axis: { printf("type: Axis, "); break; } case kIOHIDElementTypeInput_ScanCodes: { printf("type: ScanCodes, "); break; } case kIOHIDElementTypeOutput: { printf("type: Output, "); break; } case kIOHIDElementTypeFeature: { printf("type: Feature, "); break; } case kIOHIDElementTypeCollection: { IOHIDElementCollectionType tIOHIDElementCollectionType = IOHIDElementGetCollectionType(inIOHIDElementRef); switch (tIOHIDElementCollectionType) { case kIOHIDElementCollectionTypePhysical: { printf("type: Physical Collection, "); break; } case kIOHIDElementCollectionTypeApplication: { printf("type: Application Collection, "); break; } case kIOHIDElementCollectionTypeLogical: { printf("type: Logical Collection, "); break; } case kIOHIDElementCollectionTypeReport: { printf("type: Report Collection, "); break; } case kIOHIDElementCollectionTypeNamedArray: { printf("type: Named Array Collection, "); break; } case kIOHIDElementCollectionTypeUsageSwitch: { printf("type: Usage Switch Collection, "); break; } case kIOHIDElementCollectionTypeUsageModifier: { printf("type: Usage Modifier Collection, "); break; } default: { printf("type: %p Collection, ", (void *) tIOHIDElementCollectionType); break; } } // switch break; } default: { printf("type: %p, ", (void *) tIOHIDElementType); break; } } /* switch */ uint32_t usagePage = IOHIDElementGetUsagePage(inIOHIDElementRef); uint32_t usage = IOHIDElementGetUsage(inIOHIDElementRef); printf("usage: 0x%04lX:0x%04lX, ", (long unsigned int) usagePage, (long unsigned int) usage); CFStringRef tCFStringRef = HIDCopyUsageName(usagePage, usage); if (tCFStringRef) { char usageString[256] = ""; (void) CFStringGetCString(tCFStringRef, usageString, sizeof(usageString), kCFStringEncodingUTF8); printf("\"%s\", ", usageString); CFRelease(tCFStringRef); } CFStringRef nameCFStringRef = IOHIDElementGetName(inIOHIDElementRef); char buffer[256]; if ( nameCFStringRef && CFStringGetCString(nameCFStringRef, buffer, sizeof(buffer), kCFStringEncodingUTF8) ) { printf("name: %s, ", buffer); } uint32_t reportID = IOHIDElementGetReportID(inIOHIDElementRef); uint32_t reportSize = IOHIDElementGetReportSize(inIOHIDElementRef); uint32_t reportCount = IOHIDElementGetReportCount(inIOHIDElementRef); printf("report: { ID: %lu, Size: %lu, Count: %lu }, ", (long unsigned int) reportID, (long unsigned int) reportSize, (long unsigned int) reportCount); uint32_t unit = IOHIDElementGetUnit(inIOHIDElementRef); uint32_t unitExp = IOHIDElementGetUnitExponent(inIOHIDElementRef); if (unit || unitExp) { printf("unit: %lu * 10^%lu, ", (long unsigned int) unit, (long unsigned int) unitExp); } CFIndex logicalMin = IOHIDElementGetLogicalMin(inIOHIDElementRef); CFIndex logicalMax = IOHIDElementGetLogicalMax(inIOHIDElementRef); if (logicalMin != logicalMax) { printf("logical: {min: %ld, max: %ld}, ", logicalMin, logicalMax); } CFIndex physicalMin = IOHIDElementGetPhysicalMin(inIOHIDElementRef); CFIndex physicalMax = IOHIDElementGetPhysicalMax(inIOHIDElementRef); if (physicalMin != physicalMax) { printf("physical: {min: %ld, max: %ld}, ", physicalMin, physicalMax); } Boolean isVirtual = IOHIDElementIsVirtual(inIOHIDElementRef); if (isVirtual) { printf("isVirtual, "); } Boolean isRelative = IOHIDElementIsRelative(inIOHIDElementRef); if (isRelative) { printf("isRelative, "); } Boolean isWrapping = IOHIDElementIsWrapping(inIOHIDElementRef); if (isWrapping) { printf("isWrapping, "); } Boolean isArray = IOHIDElementIsArray(inIOHIDElementRef); if (isArray) { printf("isArray, "); } Boolean isNonLinear = IOHIDElementIsNonLinear(inIOHIDElementRef); if (isNonLinear) { printf("isNonLinear, "); } Boolean hasPreferredState = IOHIDElementHasPreferredState(inIOHIDElementRef); if (hasPreferredState) { printf("hasPreferredState, "); } Boolean hasNullState = IOHIDElementHasNullState(inIOHIDElementRef); if (hasNullState) { printf("hasNullState, "); } printf(" }\n"); } } // HIDDumpElementInfo
static void iohidmanager_hid_device_input_callback(void *data, IOReturn result, void* sender, IOHIDValueRef value) { iohidmanager_hid_t *hid = (iohidmanager_hid_t*)hid_driver_get_data(); struct iohidmanager_hid_adapter *adapter = (struct iohidmanager_hid_adapter*)data; IOHIDElementRef element = IOHIDValueGetElement(value); uint32_t type = (uint32_t)IOHIDElementGetType(element); uint32_t page = (uint32_t)IOHIDElementGetUsagePage(element); uint32_t use = (uint32_t)IOHIDElementGetUsage(element); uint32_t cookie = (uint32_t)IOHIDElementGetCookie(element); apple_input_rec_t *tmp = NULL; if (type != kIOHIDElementTypeInput_Misc) if (type != kIOHIDElementTypeInput_Button) if (type != kIOHIDElementTypeInput_Axis) return; /* Joystick handler. * TODO: Can GamePad work the same? */ int pushed_button = 0; switch (page) { case kHIDPage_GenericDesktop: switch (type) { case kIOHIDElementTypeInput_Misc: switch (use) { case kHIDUsage_GD_Hatswitch: { tmp = adapter->hats; while(tmp && tmp->cookie != (IOHIDElementCookie)cookie) tmp = tmp->next; if(tmp->cookie == (IOHIDElementCookie)cookie) { CFIndex range = IOHIDElementGetLogicalMax(element) - IOHIDElementGetLogicalMin(element); CFIndex val = IOHIDValueGetIntegerValue(value); if(range == 3) val *= 2; switch(val) { case 0: /* pos = up */ hid->hats[adapter->slot][0] = 0; hid->hats[adapter->slot][1] = -1; break; case 1: /* pos = up+right */ hid->hats[adapter->slot][0] = 1; hid->hats[adapter->slot][1] = -1; break; case 2: /* pos = right */ hid->hats[adapter->slot][0] = 1; hid->hats[adapter->slot][1] = 0; break; case 3: /* pos = down+right */ hid->hats[adapter->slot][0] = 1; hid->hats[adapter->slot][1] = 1; break; case 4: /* pos = down */ hid->hats[adapter->slot][0] = 0; hid->hats[adapter->slot][1] = 1; break; case 5: /* pos = down+left */ hid->hats[adapter->slot][0] = -1; hid->hats[adapter->slot][1] = 1; break; case 6: /* pos = left */ hid->hats[adapter->slot][0] = -1; hid->hats[adapter->slot][1] = 0; break; case 7: /* pos = up_left */ hid->hats[adapter->slot][0] = -1; hid->hats[adapter->slot][1] = -1; break; default: /* pos = centered */ hid->hats[adapter->slot][0] = 0; hid->hats[adapter->slot][1] = 0; break; } } } break; default: tmp = adapter->axes; while(tmp && tmp->cookie != (IOHIDElementCookie)cookie) tmp = tmp->next; if (tmp) { if(tmp->cookie == (IOHIDElementCookie)cookie) { CFIndex min = IOHIDElementGetPhysicalMin(element); CFIndex state = IOHIDValueGetIntegerValue(value) - min; CFIndex max = IOHIDElementGetPhysicalMax(element) - min; float val = (float)state / (float)max; hid->axes[adapter->slot][tmp->id] = ((val * 2.0f) - 1.0f) * 32767.0f; } } else pushed_button = 1; break; } break; } break; case kHIDPage_Consumer: case kHIDPage_Button: switch (type) { case kIOHIDElementTypeInput_Misc: case kIOHIDElementTypeInput_Button: pushed_button = 1; break; } break; } if (pushed_button) { tmp = adapter->buttons; uint8_t bit = 0; while(tmp && tmp->cookie != (IOHIDElementCookie)cookie) { bit++; tmp = tmp->next; } if(tmp && tmp->cookie == (IOHIDElementCookie)cookie) { CFIndex state = IOHIDValueGetIntegerValue(value); if (state) BIT64_SET(hid->buttons[adapter->slot], bit); else BIT64_CLEAR(hid->buttons[adapter->slot], bit); } } }
static void iohidmanager_hid_device_input_callback(void *data, IOReturn result, void* sender, IOHIDValueRef value) { iohidmanager_hid_t *hid = (iohidmanager_hid_t*)hid_driver_get_data(); struct iohidmanager_hid_adapter *adapter = (struct iohidmanager_hid_adapter*)data; IOHIDElementRef element = IOHIDValueGetElement(value); uint32_t type = IOHIDElementGetType(element); uint32_t page = IOHIDElementGetUsagePage(element); uint32_t use = IOHIDElementGetUsage(element); if (type != kIOHIDElementTypeInput_Misc) if (type != kIOHIDElementTypeInput_Button) if (type != kIOHIDElementTypeInput_Axis) return; /* Joystick handler. * TODO: Can GamePad work the same? */ switch (page) { case kHIDPage_GenericDesktop: switch (type) { case kIOHIDElementTypeInput_Misc: switch (use) { case kHIDUsage_GD_Hatswitch: break; default: { int i; // +0/-0 => Left Stick Horizontal => 48 // +1/-1 => Left Stick Vertical => 49 // +2/-2 => Right Stick Horizontal => 51 // +3/-3 => Right Stick Vertical => 52 // +4/-4 => Left Trigger (if exists) => 50 // +5/-5 => Right Trigger (if exists) => 53 static const uint32_t axis_use_ids[6] = { 48, 49, 51, 52, 50, 53 }; for (i = 0; i < 6; i ++) { CFIndex min = IOHIDElementGetPhysicalMin(element); CFIndex state = IOHIDValueGetIntegerValue(value) - min; CFIndex max = IOHIDElementGetPhysicalMax(element) - min; float val = (float)state / (float)max; if (use != axis_use_ids[i]) continue; hid->axes[adapter->slot][i] = ((val * 2.0f) - 1.0f) * 32767.0f; } } break; } break; } break; case kHIDPage_Button: switch (type) { case kIOHIDElementTypeInput_Button: { CFIndex state = IOHIDValueGetIntegerValue(value); unsigned id = use - 1; if (state) BIT64_SET(hid->buttons[adapter->slot], id); else BIT64_CLEAR(hid->buttons[adapter->slot], id); } break; } break; } }