HID_Calibrator/HID Utilities/HID_Config_Utilities.c

//     File: HID_Config_Utilities.c
// Abstract: Implementation of the HID configuration utilities
//  Version: 2.0
// 
// Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple
// Inc. ("Apple") in consideration of your agreement to the following
// terms, and your use, installation, modification or redistribution of
// this Apple software constitutes acceptance of these terms.  If you do
// not agree with these terms, please do not use, install, modify or
// redistribute this Apple software.
// 
// In consideration of your agreement to abide by the following terms, and
// subject to these terms, Apple grants you a personal, non-exclusive
// license, under Apple's copyrights in this original Apple software (the
// "Apple Software"), to use, reproduce, modify and redistribute the Apple
// Software, with or without modifications, in source and/or binary forms;
// provided that if you redistribute the Apple Software in its entirety and
// without modifications, you must retain this notice and the following
// text and disclaimers in all such redistributions of the Apple Software.
// Neither the name, trademarks, service marks or logos of Apple Inc. may
// be used to endorse or promote products derived from the Apple Software
// without specific prior written permission from Apple.  Except as
// expressly stated in this notice, no other rights or licenses, express or
// implied, are granted by Apple herein, including but not limited to any
// patent rights that may be infringed by your derivative works or by other
// works in which the Apple Software may be incorporated.
// 
// The Apple Software is provided by Apple on an "AS IS" basis.  APPLE
// MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
// THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
// OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
// 
// IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
// MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
// AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
// STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
// 
// Copyright (C) 2014 Apple Inc. All Rights Reserved.
// 
// *****************************************************
#define LOG_SCORING    0
 
#include <stdlib.h>                                                             // malloc
#include <time.h>                                                               // clock
 
#include <AssertMacros.h>
 
#include "HID_Utilities_External.h"
 
// ---------------------------------
 
// polls single device's elements for a change greater than kPercentMove.  Times out after given time
// returns 1 and pointer to element if found
// returns 0 and NULL for both parameters if not found
 
unsigned char HIDConfigureSingleDeviceAction(IOHIDDeviceRef     inIOHIDDeviceRef,
                                             IOHIDElementRef *  outIOHIDElementRef,
                                             double             timeout) {
    if (!inIOHIDDeviceRef) {
        return (0);
    }
    if (0 == HIDHaveDeviceList()) {                                             // if we do not have a device list
        return (0);                                                             // return 0
    }
 
    Boolean found = false;
 
    // build list of device and elements to save current values
    CFIndex maxElements = HIDCountDeviceElements(inIOHIDDeviceRef,
                                                 kHIDElementTypeInput);
    double *saveValueArray = (double *) calloc(maxElements,
                                               sizeof(double));                 // 2D array to save values
 
    // store initial values on first pass / compare to initial value on subsequent passes
    Boolean first = true;
 
    // get all the elements from this device
    CFArrayRef elementCFArrayRef = IOHIDDeviceCopyMatchingElements(inIOHIDDeviceRef,
                                                                   NULL,
                                                                   kIOHIDOptionsTypeNone);
    // if that worked...
    if (elementCFArrayRef) {
        clock_t start = clock(),
                end;
 
        // poll all devices and elements
        while (!found) {
            uint32_t currElementIndex = 0;
            CFIndex idx,
                    cnt = CFArrayGetCount(elementCFArrayRef);
            for (idx = 0; idx < cnt; idx++) {
                *outIOHIDElementRef = (IOHIDElementRef) CFArrayGetValueAtIndex(elementCFArrayRef,
                                                                               idx);
                if (!*outIOHIDElementRef) {
                    continue;
                }
 
                // is this an input element?
                IOHIDElementType type = IOHIDElementGetType(*outIOHIDElementRef);
 
                switch (type) {
                    // these types are inputs
                    case kIOHIDElementTypeInput_Misc:
                    case kIOHIDElementTypeInput_Button:
                    case kIOHIDElementTypeInput_Axis:
                    case kIOHIDElementTypeInput_ScanCodes:
                    default:
                    {
                        break;
                    }
 
                    case kIOHIDElementTypeOutput:
                    case kIOHIDElementTypeFeature:
                    case kIOHIDElementTypeCollection:
                    {
                        *outIOHIDElementRef = NULL;                             // these types are not (Skip them)
                        break;
                    }
                }                                                               /* switch */
                if (!*outIOHIDElementRef) {
                    continue;                                                   // skip this element
                }
 
                // get this elements current value
                double value = 0;                                               // default value is zero
                IOHIDValueRef tIOHIDValueRef;
                IOReturn ioReturn = IOHIDDeviceGetValue(inIOHIDDeviceRef,
                                                        *outIOHIDElementRef,
                                                        &tIOHIDValueRef);
                if (kIOReturnSuccess == ioReturn) {
                    value = IOHIDValueGetScaledValue(tIOHIDValueRef,
                                                     kIOHIDValueScaleTypePhysical);
                }
                if (first) {
                    saveValueArray[currElementIndex] = value;
                } else {
                    CFIndex min = IOHIDElementGetLogicalMin(*outIOHIDElementRef);
                    CFIndex max = IOHIDElementGetLogicalMax(*outIOHIDElementRef);
 
                    double initialValue = saveValueArray[currElementIndex];
                    double delta = (double) (max -
                                             min) *
                                   kPercentMove *
                                   0.01f;
                    // is the new value within +/- delta of the initial value?
                    if (((initialValue +
                          delta) < value) ||
                        ((initialValue -
                          delta) > value))
                    {
                        found = 1;                                              // (yes!) mark as found
                        break;
                    }
                }                                                               // if (first)
 
                currElementIndex++;                                             // bump element index
            }                                                                   // next idx
            if (first) {
                first = false;                                                  // no longer the first pass
            } else {
                // are we done?
                end = clock();
                double secs = (double) (end -
                                        start) /
                              CLOCKS_PER_SEC;
                if (secs > timeout) {
                    break;                                                      // (yes) timeout
                }
            }
        }                                                                       // while (!found)
 
        CFRelease(elementCFArrayRef);
    }                                                                           // if (elementCFArrayRef)
    if (saveValueArray) {
        free(saveValueArray);
    }
    // return device and element moved
    if (found) {
        return (1);
    } else {
        *outIOHIDElementRef = NULL;
 
        return (0);
    }
}                                                                               // HIDConfigureSingleDeviceAction
 
// *************************************************************************
//
// 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
 
// *************************************************************************
//
// 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  - true if successful
// outIOHIDDeviceRef        - the device
// outIOHIDElementRef       - the element
//
Boolean HIDConfigureAction(IOHIDDeviceRef * outIOHIDDeviceRef,
                           IOHIDElementRef *outIOHIDElementRef,
                           double           inTimeout) {
    return (HIDConfigureActionOfType(kActionTypeAll,
                                     inTimeout,
                                     outIOHIDDeviceRef,
                                     outIOHIDElementRef));
}                                                                               // HIDConfigureAction
 
// *************************************************************************
//
// HIDSaveElementPref(inKeyCFStringRef, inAppCFStringRef, inIOHIDDeviceRef, inIOHIDElementRef)
//
// Purpose: Save the device & element values into the specified key in the specified applications preferences
//
// Inputs:  inKeyCFStringRef    - the preference key
// inAppCFStringRef - the application identifier
// inIOHIDDeviceRef         - the device
// inIOHIDElementRef            - the element
// Returns: Boolean             - if successful
//
 
Boolean HIDSaveElementPref(const CFStringRef    inKeyCFStringRef,
                           CFStringRef          inAppCFStringRef,
                           IOHIDDeviceRef       inIOHIDDeviceRef,
                           IOHIDElementRef      inIOHIDElementRef) {
    Boolean success = false;
 
    if (inKeyCFStringRef &&
        inAppCFStringRef &&
        inIOHIDDeviceRef &&
        inIOHIDElementRef)
    {
        uint32_t vendorID = IOHIDDevice_GetVendorID(inIOHIDDeviceRef);
        require(vendorID,
                Oops);
 
        uint32_t productID = IOHIDDevice_GetProductID(inIOHIDDeviceRef);
        require(productID,
                Oops);
 
        uint32_t locID = IOHIDDevice_GetLocationID(inIOHIDDeviceRef);
        require(locID,
                Oops);
 
        uint32_t usagePage = IOHIDDevice_GetUsagePage(inIOHIDDeviceRef);
        uint32_t usage = IOHIDDevice_GetUsage(inIOHIDDeviceRef);
        if (!usagePage ||
            !usage)
        {
            usagePage = IOHIDDevice_GetPrimaryUsagePage(inIOHIDDeviceRef);
            usage = IOHIDDevice_GetPrimaryUsage(inIOHIDDeviceRef);
        }
 
        require(usagePage &&
                usage,
                Oops);
 
        uint32_t usagePageE = IOHIDElementGetUsagePage(inIOHIDElementRef);
        uint32_t usageE = IOHIDElementGetUsage(inIOHIDElementRef);
        IOHIDElementCookie eleCookie = IOHIDElementGetCookie(inIOHIDElementRef);
 
        CFStringRef prefCFStringRef = CFStringCreateWithFormat(kCFAllocatorDefault,
                                                               NULL,
                                                               CFSTR("d:{v:%d, p:%d, l:%d, p:%d, u:%d}, e:{p:%d, u:%d, c:%d}"),
                                                               vendorID,
                                                               productID,
                                                               locID,
                                                               usagePage,
                                                               usage,
                                                               usagePageE,
                                                               usageE,
                                                               eleCookie);
        if (prefCFStringRef) {
            CFPreferencesSetAppValue(inKeyCFStringRef,
                                     prefCFStringRef,
                                     inAppCFStringRef);
            CFRelease(prefCFStringRef);
            success = true;
        }
    }
 
Oops:;
 
    return (success);
}                                                                               // HIDSaveElementPref
 
// *************************************************************************
//
// HIDRestoreElementPref(inKeyCFStringRef, inAppCFStringRef, outIOHIDDeviceRef, outIOHIDElementRef)
//
// Purpose: Find the specified preference in the specified application
//
// Inputs:  inKeyCFStringRef    - the preference key
// inAppCFStringRef - the application identifier
// outIOHIDDeviceRef    - address where to restore the device
// outIOHIDElementRef   - address where to restore the element
// Returns: Boolean             - if successful
// outIOHIDDeviceRef    - the device
// outIOHIDElementRef   - the element
//
 
Boolean HIDRestoreElementPref(CFStringRef       inKeyCFStringRef,
                              CFStringRef       inAppCFStringRef,
                              IOHIDDeviceRef *  outIOHIDDeviceRef,
                              IOHIDElementRef * outIOHIDElementRef) {
    Boolean found = false;
 
    if (inKeyCFStringRef &&
        inAppCFStringRef &&
        outIOHIDDeviceRef &&
        outIOHIDElementRef)
    {
        CFPropertyListRef prefCFPropertyListRef = CFPreferencesCopyAppValue(inKeyCFStringRef,
                                                                            inAppCFStringRef);
        if (prefCFPropertyListRef) {
            if (CFStringGetTypeID() == CFGetTypeID(prefCFPropertyListRef)) {
                char buffer[256];
                if (CFStringGetCString((CFStringRef) prefCFPropertyListRef,
                                       buffer,
                                       sizeof(buffer),
                                       kCFStringEncodingUTF8))
                {
                    HID_info_rec searchHIDInfo;
 
                    uint32_t count = sscanf(buffer,
                                            "d:{v:%d, p:%d, l:%d, p:%d, u:%d}, e:{p:%d, u:%d, c:%d}",
                                            &searchHIDInfo.device.vendorID,
                                            &searchHIDInfo.device.productID,
                                            &searchHIDInfo.device.locID,
                                            &searchHIDInfo.device.usagePage,
                                            &searchHIDInfo.device.usage,
                                            &searchHIDInfo.element.usagePage,
                                            &searchHIDInfo.element.usage,
                                            (IOHIDElementCookie *) &searchHIDInfo.element.cookie);
                    if (8 == count) {                                           // if we found all eight parametersā€¦
                                                                                // and can find a device & element that matches
                                                                                // theseā€¦
                        if (HIDFindDeviceAndElement(&searchHIDInfo,
                                                    outIOHIDDeviceRef,
                                                    outIOHIDElementRef))
                        {
                            found = true;
                        }
                    }
                }
            } else {
                // We found the entry with this key but it's the wrong type; delete it.
                CFPreferencesSetAppValue(inKeyCFStringRef,
                                         NULL,
                                         inAppCFStringRef);
                (void) CFPreferencesAppSynchronize(inAppCFStringRef);
            }
 
            CFRelease(prefCFPropertyListRef);
        }
    }
 
    return (found);
}                                                                               // HIDRestoreElementPref
 
// *************************************************************************
//
// HIDFindDeviceAndElement(inSearchInfo, outFoundDevice, outFoundElement)
//
// Purpose: find the closest matching device and element for this action
//
// Notes:   matches device: serial, vendorID, productID, location, inUsagePage, usage
// matches element: cookie, inUsagePage, usage,
//
// Inputs:  inSearchInfo    - the device & element info we searching for
// outFoundDevice   - the address of the best matching device
// outFoundElement  - the address of the best matching element
//
// Returns: Boolean         - true if we find a match
// outFoundDevice   - the best matching device
// outFoundElement  - the best matching element
//
 
Boolean HIDFindDeviceAndElement(const HID_info_rec *inSearchInfo,
                                IOHIDDeviceRef *    outFoundDevice,
                                IOHIDElementRef *   outFoundElement) {
    Boolean result = false;
 
    IOHIDDeviceRef bestIOHIDDeviceRef = NULL;
    IOHIDElementRef bestIOHIDElementRef = NULL;
    int bestScore = 0;
 
    CFIndex devIndex,
            devCount = CFArrayGetCount(gDeviceCFArrayRef);
 
    for (devIndex = 0; devIndex < devCount; devIndex++) {
        int deviceScore = 1;
 
        IOHIDDeviceRef tIOHIDDeviceRef = (IOHIDDeviceRef) CFArrayGetValueAtIndex(gDeviceCFArrayRef,
                                                                                 devIndex);
        if (!tIOHIDDeviceRef) {
            continue;
        }
        // match vendorID, productID (+10, +8)
        if (inSearchInfo->device.vendorID) {
            uint32_t vendorID = IOHIDDevice_GetVendorID(tIOHIDDeviceRef);
            if (vendorID) {
                if (inSearchInfo->device.vendorID == vendorID) {
                    deviceScore += 10;
                    if (inSearchInfo->device.productID) {
                        uint32_t productID = IOHIDDevice_GetProductID(tIOHIDDeviceRef);
                        if (productID) {
                            if (inSearchInfo->device.productID == productID) {
                                deviceScore += 8;
                            }                                                   // if (inSearchInfo->device.productID == productID)
                        }                                                       // if (productID)
                    }                                                           // if (inSearchInfo->device.productID)
                }                                                               // if (inSearchInfo->device.vendorID == vendorID)
            }                                                                   // if vendorID
        }                                                                       // if search->device.vendorID
                                                                                // match usagePage & usage (+9)
        if (inSearchInfo->device.usagePage &&
            inSearchInfo->device.usage)
        {
            uint32_t usagePage = IOHIDDevice_GetUsagePage(tIOHIDDeviceRef);
            uint32_t usage = IOHIDDevice_GetUsage(tIOHIDDeviceRef);
            if (!usagePage ||
                !usage)
            {
                usagePage = IOHIDDevice_GetPrimaryUsagePage(tIOHIDDeviceRef);
                usage = IOHIDDevice_GetPrimaryUsage(tIOHIDDeviceRef);
            }
            if (usagePage) {
                if (inSearchInfo->device.usagePage == usagePage) {
                    if (usage) {
                        if (inSearchInfo->device.usage == usage) {
                            deviceScore += 9;
                        }                                                       // if (inSearchInfo->usage == usage)
                    }                                                           // if (usage)
                }                                                               // if (inSearchInfo->usagePage == usagePage)
            }                                                                   // if (usagePage)
        }                                                                       // if (inSearchInfo->usagePage &&
                                                                                // inSearchInfo->usage)
                                                                                // match location ID (+5)
        if (inSearchInfo->device.locID) {
            uint32_t locID = IOHIDDevice_GetLocationID(tIOHIDDeviceRef);
            if (locID) {
                if (inSearchInfo->device.locID == locID) {
                    deviceScore += 5;
                }
            }
        }
 
        // iterate over all elements of this device
        gElementCFArrayRef = IOHIDDeviceCopyMatchingElements(tIOHIDDeviceRef,
                                                             NULL,
                                                             0);
        if (gElementCFArrayRef) {
            CFIndex eleIndex,
                    eleCount = CFArrayGetCount(gElementCFArrayRef);
            for (eleIndex = 0; eleIndex < eleCount; eleIndex++) {
                IOHIDElementRef tIOHIDElementRef = (IOHIDElementRef) CFArrayGetValueAtIndex(gElementCFArrayRef,
                                                                                            eleIndex);
                if (!tIOHIDElementRef) {
                    continue;
                }
 
                int score = deviceScore;
                // match usage page, usage & cookie
                if (inSearchInfo->element.usagePage &&
                    inSearchInfo->element.usage)
                {
                    uint32_t usagePage = IOHIDElementGetUsagePage(tIOHIDElementRef);
                    if (inSearchInfo->element.usagePage == usagePage) {
                        uint32_t usage = IOHIDElementGetUsage(tIOHIDElementRef);
                        if (inSearchInfo->element.usage == usage) {
                            score += 5;
                            IOHIDElementCookie cookie = IOHIDElementGetCookie(tIOHIDElementRef);
                            if (inSearchInfo->element.cookie == cookie) {
                                score += 4;
                            }                                                   // cookies match
                        } else {
                            score = 0;
                        }                                                       // usages match
                    } else {
                        score = 0;
                    }                                                           // usage pages match
                }                                                               // if (search usage page & usage)
 
#if LOG_SCORING
                if (kHIDPage_KeyboardOrKeypad != tElementRef->usagePage) {      // skip keyboards here
                    printf("%s: (%ld:%ld)-I-Debug, score: %ld\t",
                           __PRETTY_FUNCTION__,
                           inSearchInfo->element.usagePage,
                           inSearchInfo->element.usage,
                           score);
                    HIDPrintElement(tIOHIDElementRef);
                }
 
#endif                                                                          // LOG_SCORING
                if (score > bestScore) {
                    bestIOHIDDeviceRef = tIOHIDDeviceRef;
                    bestIOHIDElementRef = tIOHIDElementRef;
                    bestScore = score;
#if LOG_SCORING
                    printf("%s: (%ld:%ld)-I-Debug, better score: %ld\t",
                           __PRETTY_FUNCTION__,
                           inSearchInfo->element.usagePage,
                           inSearchInfo->element.usage,
                           score);
                    HIDPrintElement(bestIOHIDElementRef);
#endif                                                                          // LOG_SCORING
                }
            }                                                                   // for elements...
 
            CFRelease(gElementCFArrayRef);
            gElementCFArrayRef = NULL;
        }                                                                       // if (gElementCFArrayRef)
    }                                                                           // for (devIndex = 0; devIndex < devCount;
                                                                                // devIndex++)
    if (bestIOHIDDeviceRef ||
        bestIOHIDElementRef)
    {
        *outFoundDevice = bestIOHIDDeviceRef;
        *outFoundElement = bestIOHIDElementRef;
#if LOG_SCORING
        printf("%s: (%ld:%ld)-I-Debug, best score: %ld\t",
               __PRETTY_FUNCTION__,
               inSearchInfo->element.usagePage,
               inSearchInfo->element.usage,
               bestScore);
        HIDPrintElement(bestIOHIDElementRef);
#endif                                                                          // LOG_SCORING
        result = true;
    }
 
    return (result);
}                                                                               // HIDFindDeviceAndElement
 
// ---------------------------------
 
// takes input records, save required info
// assume file is open and at correct position.
// will always write to file (if file exists) size of HID_info_rec, even if device and or element is bad
 
void HIDSaveElementConfig(FILE *                fileRef,
                          IOHIDDeviceRef        inIOHIDDeviceRef,
                          IOHIDElementRef       inIOHIDElementRef,
                          IOHIDElementCookie    actionCookie) {
    // must save:
    // actionCookie
    // Device: serial,vendorID, productID, location, usagePage, usage
    // Element: cookie, usagePage, usage,
    HID_info_rec hidInfoRec;
 
    HIDSetElementConfig(&hidInfoRec,
                        inIOHIDDeviceRef,
                        inIOHIDElementRef,
                        actionCookie);
    // write to file
    if (fileRef) {
        fwrite((void *) &hidInfoRec,
               sizeof(HID_info_rec),
               1,
               fileRef);
    }
}                                                                               // HIDSaveElementConfig
 
// ---------------------------------
 
// take file, read one record (assume file position is correct and file is open)
// search for matching device
// return IOHIDDeviceRef, IOHIDElementRef and cookie for action
 
IOHIDElementCookie HIDRestoreElementConfig(FILE *           fileRef,
                                           IOHIDDeviceRef * outIOHIDDeviceRef,
                                           IOHIDElementRef *outIOHIDElementRef) {
    // Device: serial,vendorID, productID, location, usagePage, usage
    // Element: cookie, usagePage, usage,
 
    HID_info_rec hidInfoRec;
 
    fread((void *) &hidInfoRec,
          1,
          sizeof(HID_info_rec),
          fileRef);
 
    return (HIDGetElementConfig(&hidInfoRec,
                                outIOHIDDeviceRef,
                                outIOHIDElementRef));
}                                                                               // HIDRestoreElementConfig
 
// ---------------------------------
 
// Set up a config record for saving
// takes an input records, returns record user can save as they want
// Note: the save rec must be pre-allocated by the calling app and will be filled out
void HIDSetElementConfig(HID_info_ptr       inHIDInfoPtr,
                         IOHIDDeviceRef     inIOHIDDeviceRef,
                         IOHIDElementRef    inIOHIDElementRef,
                         IOHIDElementCookie actionCookie) {
    // must save:
    // actionCookie
    // Device: serial,vendorID, productID, location, usagePage, usage
    // Element: cookie, usagePage, usage,
    inHIDInfoPtr->actionCookie = actionCookie;
    // device
    // need to add serial number when I have a test case
    if (inIOHIDDeviceRef &&
        inIOHIDElementRef)
    {
        inHIDInfoPtr->device.vendorID = IOHIDDevice_GetVendorID(inIOHIDDeviceRef);
        inHIDInfoPtr->device.productID = IOHIDDevice_GetProductID(inIOHIDDeviceRef);
        inHIDInfoPtr->device.locID = IOHIDDevice_GetLocationID(inIOHIDDeviceRef);
        inHIDInfoPtr->device.usage = IOHIDDevice_GetUsage(inIOHIDDeviceRef);
        inHIDInfoPtr->device.usagePage = IOHIDDevice_GetUsagePage(inIOHIDDeviceRef);
        if (!inHIDInfoPtr->device.usagePage ||
            !inHIDInfoPtr->device.usage)
        {
            inHIDInfoPtr->device.usagePage = IOHIDDevice_GetPrimaryUsagePage(inIOHIDDeviceRef);
            inHIDInfoPtr->device.usage = IOHIDDevice_GetPrimaryUsage(inIOHIDDeviceRef);
        }
 
        inHIDInfoPtr->element.usagePage = IOHIDElementGetUsagePage(inIOHIDElementRef);
        inHIDInfoPtr->element.usage = IOHIDElementGetUsage(inIOHIDElementRef);
        inHIDInfoPtr->element.minReport = IOHIDElement_GetCalibrationSaturationMin(inIOHIDElementRef);
        inHIDInfoPtr->element.maxReport = IOHIDElement_GetCalibrationSaturationMax(inIOHIDElementRef);
        inHIDInfoPtr->element.cookie = IOHIDElementGetCookie(inIOHIDElementRef);
    } else {
        inHIDInfoPtr->device.vendorID = 0;
        inHIDInfoPtr->device.productID = 0;
        inHIDInfoPtr->device.locID = 0;
        inHIDInfoPtr->device.usage = 0;
        inHIDInfoPtr->device.usagePage = 0;
 
        inHIDInfoPtr->element.usagePage = 0;
        inHIDInfoPtr->element.usage = 0;
        inHIDInfoPtr->element.minReport = 0;
        inHIDInfoPtr->element.maxReport = 0;
        inHIDInfoPtr->element.cookie = 0;
    }
}                                                                               // HIDSetElementConfig
 
// ---------------------------------
#if false                                                                       // debug utility function to dump config record
void HIDDumpConfig(HID_info_ptr inHIDInfoPtr) {
    printf(
        "Config Record for action: %d\n\t vendor: %d    product: %d    location: %d\n\t usage: %d    usagePage: %d\n\t element.usagePage: %d    element.usage: %d\n\t minReport: %d    maxReport: %d\n\t cookie: %d\n",
        inHIDInfoPtr->actionCookie,
        inHIDInfoPtr->device.vendorID,
        inHIDInfoPtr->device.productID,
        inHIDInfoPtr->locID,
        inHIDInfoPtr->usage,
        inHIDInfoPtr->usagePage,
        inHIDInfoPtr->element.usagePage,
        inHIDInfoPtr->element.usage,
        inHIDInfoPtr->minReport,
        inHIDInfoPtr->maxReport,
        inHIDInfoPtr->cookie);
}                                                                               // HIDDumpConfig
#endif // 0
       // ---------------------------------
 
// Get matching element from config record
// takes a pre-allocated and filled out config record
// search for matching device
// return IOHIDDeviceRef, IOHIDElementRef and cookie for action
IOHIDElementCookie HIDGetElementConfig(HID_info_ptr     inHIDInfoPtr,
                                       IOHIDDeviceRef * outIOHIDDeviceRef,
                                       IOHIDElementRef *outIOHIDElementRef) {
    if (!inHIDInfoPtr->device.locID &&
        !inHIDInfoPtr->device.vendorID &&
        !inHIDInfoPtr->device.productID &&
        !inHIDInfoPtr->device.usage &&
        !inHIDInfoPtr->device.usagePage)                                        //
    {                                                                           //
                                                                                // early out
        *outIOHIDDeviceRef = NULL;
        *outIOHIDElementRef = NULL;
 
        return (inHIDInfoPtr->actionCookie);
    }
 
    IOHIDDeviceRef tIOHIDDeviceRef = NULL,
                   foundIOHIDDeviceRef = NULL;
    IOHIDElementRef tIOHIDElementRef = NULL,
                    foundIOHIDElementRef = NULL;
 
    CFIndex devIdx,
            devCnt,
            idx,
            cnt;
    // compare to current device list for matches
    // look for device
    if (inHIDInfoPtr->device.locID &&
        inHIDInfoPtr->device.vendorID &&
        inHIDInfoPtr->device.productID)                                         // look for specific
    {                                                                           // device
                                                                                // type plug in to same
                                                                                // port
        devCnt = CFArrayGetCount(gDeviceCFArrayRef);
        for (devIdx = 0; devIdx < devCnt; devIdx++) {
            tIOHIDDeviceRef = (IOHIDDeviceRef) CFArrayGetValueAtIndex(gDeviceCFArrayRef,
                                                                      devIdx);
            if (!tIOHIDDeviceRef) {
                continue;                                                       // skip this device
            }
 
            uint32_t locID = IOHIDDevice_GetLocationID(tIOHIDDeviceRef);
            uint32_t vendorID = IOHIDDevice_GetVendorID(tIOHIDDeviceRef);
            uint32_t productID = IOHIDDevice_GetProductID(tIOHIDDeviceRef);
            if ((inHIDInfoPtr->device.locID == locID) &&
                (inHIDInfoPtr->device.vendorID == vendorID) &&
                (inHIDInfoPtr->device.productID == productID))
            {
                foundIOHIDDeviceRef = tIOHIDDeviceRef;
            }
            if (foundIOHIDDeviceRef) {
                break;
            }
        }                                                                       // next devIdx
        if (foundIOHIDDeviceRef) {
            CFArrayRef elementCFArrayRef = IOHIDDeviceCopyMatchingElements(foundIOHIDDeviceRef,
                                                                           NULL,
                                                                           kIOHIDOptionsTypeNone);
            if (elementCFArrayRef) {
                cnt = CFArrayGetCount(elementCFArrayRef);
                for (idx = 0; idx < cnt; idx++) {
                    tIOHIDElementRef = (IOHIDElementRef) CFArrayGetValueAtIndex(elementCFArrayRef,
                                                                                idx);
                    if (!tIOHIDElementRef) {
                        continue;                                               // skip this element
                    }
 
                    IOHIDElementCookie cookie = IOHIDElementGetCookie(tIOHIDElementRef);
                    if (inHIDInfoPtr->element.cookie == cookie) {
                        foundIOHIDElementRef = tIOHIDElementRef;
                    }
                    if (foundIOHIDElementRef) {
                        break;
                    }
                }
                if (!foundIOHIDElementRef) {
                    cnt = CFArrayGetCount(elementCFArrayRef);
                    for (idx = 0; idx < cnt; idx++) {
                        tIOHIDElementRef = (IOHIDElementRef) CFArrayGetValueAtIndex(elementCFArrayRef,
                                                                                    idx);
                        if (!tIOHIDElementRef) {
                            continue;                                           // skip this element
                        }
 
                        uint32_t usagePage = IOHIDElementGetUsagePage(tIOHIDElementRef);
                        uint32_t usage = IOHIDElementGetUsage(tIOHIDElementRef);
                        if ((inHIDInfoPtr->element.usage == usage) &&
                            (inHIDInfoPtr->element.usagePage == usagePage))
                        {
                            foundIOHIDElementRef = tIOHIDElementRef;
                        }
                        if (foundIOHIDElementRef) {
                            break;
                        }
                    }                                                           // next idx
                }                                                               // if (!foundIOHIDElementRef)
                if (foundIOHIDElementRef) {                                     // if same device
                                                                                // setup the calibration
                    IOHIDElement_SetupCalibration(tIOHIDElementRef);
 
                    IOHIDElement_SetCalibrationSaturationMin(tIOHIDElementRef,
                                                             inHIDInfoPtr->element.minReport);
                    IOHIDElement_SetCalibrationSaturationMax(tIOHIDElementRef,
                                                             inHIDInfoPtr->element.maxReport);
                }
 
                CFRelease(elementCFArrayRef);
            }                                                                   // if (elementCFArrayRef)
        }                                                                       // if (foundIOHIDDeviceRef)
                                                                                // if we have not found a match, look at just vendor
                                                                                // and product
        if ((!foundIOHIDDeviceRef) &&
            (inHIDInfoPtr->device.vendorID &&
             inHIDInfoPtr->device.productID))
        {
            devCnt = CFArrayGetCount(gDeviceCFArrayRef);
            for (devIdx = 0; devIdx < devCnt; devIdx++) {
                tIOHIDDeviceRef = (IOHIDDeviceRef) CFArrayGetValueAtIndex(gDeviceCFArrayRef,
                                                                          devIdx);
                if (!tIOHIDDeviceRef) {
                    continue;                                                   // skip this device
                }
 
                uint32_t vendorID = IOHIDDevice_GetVendorID(tIOHIDDeviceRef);
                uint32_t productID = IOHIDDevice_GetProductID(tIOHIDDeviceRef);
                if ((inHIDInfoPtr->device.vendorID == vendorID) &&
                    (inHIDInfoPtr->device.productID == productID))
                {
                    foundIOHIDDeviceRef = tIOHIDDeviceRef;
                }
                if (foundIOHIDDeviceRef) {
                    break;
                }
            }
            // match elements by cookie since same device type
            if (foundIOHIDDeviceRef) {
                CFArrayRef elementCFArrayRef = IOHIDDeviceCopyMatchingElements(foundIOHIDDeviceRef,
                                                                               NULL,
                                                                               kIOHIDOptionsTypeNone);
                if (elementCFArrayRef) {
                    cnt = CFArrayGetCount(elementCFArrayRef);
                    for (idx = 0; idx < cnt; idx++) {
                        tIOHIDElementRef = (IOHIDElementRef) CFArrayGetValueAtIndex(elementCFArrayRef,
                                                                                    idx);
                        if (!tIOHIDElementRef) {
                            continue;                                           // skip this element
                        }
 
                        IOHIDElementCookie cookie = IOHIDElementGetCookie(tIOHIDElementRef);
                        if (inHIDInfoPtr->element.cookie == cookie) {
                            foundIOHIDElementRef = tIOHIDElementRef;
                        }
                        if (foundIOHIDElementRef) {
                            break;
                        }
                    }
                    // if no cookie match (should NOT occur) match on usage
                    if (!foundIOHIDElementRef) {
                        cnt = CFArrayGetCount(elementCFArrayRef);
                        for (idx = 0; idx < cnt; idx++) {
                            tIOHIDElementRef = (IOHIDElementRef) CFArrayGetValueAtIndex(elementCFArrayRef,
                                                                                        idx);
                            if (!tIOHIDElementRef) {
                                continue;                                       // skip this element
                            }
 
                            uint32_t usagePage = IOHIDElementGetUsagePage(tIOHIDElementRef);
                            uint32_t usage = IOHIDElementGetUsage(tIOHIDElementRef);
                            if ((inHIDInfoPtr->element.usage == usage) &&
                                (inHIDInfoPtr->element.usagePage == usagePage))
                            {
                                foundIOHIDElementRef = tIOHIDElementRef;
                            }
                            if (foundIOHIDElementRef) {
                                break;
                            }
                        }                                                       // next idx
                    }                                                           // if (!foundIOHIDElementRef)
                    if (foundIOHIDElementRef) {                                 // if same device
                                                                                // setup the calibration
                        IOHIDElement_SetupCalibration(tIOHIDElementRef);
                        IOHIDElement_SetCalibrationSaturationMin(tIOHIDElementRef,
                                                                 inHIDInfoPtr->element.minReport);
                        IOHIDElement_SetCalibrationSaturationMax(tIOHIDElementRef,
                                                                 inHIDInfoPtr->element.maxReport);
                    }
 
                    CFRelease(elementCFArrayRef);
                }                                                               // if (elementCFArrayRef)
            }                                                                   // if (foundIOHIDDeviceRef)
        }                                                                       // if (device not found & vendorID & productID)
    }                                                                           // if (inHIDInfoPtr->locID &&
                                                                                // inHIDInfoPtr->device.vendorID &&
                                                                                // inHIDInfoPtr->device.productID)
                                                                                // can't find matching device return NULL, do not
                                                                                // return first device
    if ((!foundIOHIDDeviceRef) ||
        (!foundIOHIDElementRef))
    {
        // no HID device
        *outIOHIDDeviceRef = NULL;
        *outIOHIDElementRef = NULL;
 
        return (inHIDInfoPtr->actionCookie);
    } else {
        // HID device
        *outIOHIDDeviceRef = foundIOHIDDeviceRef;
        *outIOHIDElementRef = foundIOHIDElementRef;
 
        return (inHIDInfoPtr->actionCookie);
    }
}                                                                               // HIDGetElementConfig