HID Utilities/HID_Utilities.c

//     File: HID_Utilities.c
// Abstract: Implementation of the HID utilities
//  Version: 5.3
// 
// 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.
// 
// *****************************************************
#pragma mark - includes & imports
// -----------------------------------------------------
 
#include <AssertMacros.h>
 
#include "HID_Utilities_External.h"
 
// *****************************************************
#pragma mark - typedefs, enums, defines, etc.
// -----------------------------------------------------
#define FAKE_MISSING_NAMES       0  // for debugging; returns the vendor, product & cookie (or usage info) as numbers.
#define VERBOSE_ELEMENT_NAMES    0  // set true to include vender & product names in element names (useful for debugging)
 
#define kPercentMove           10           // precent of overall range a element must move to register
#define kNameKeyCFStringRef CFSTR("Name")   // dictionary key
 
// *****************************************************
#pragma mark - local (static) function prototypes
// -----------------------------------------------------
 
static void CFSetApplierFunctionCopyToCFArray(const void *  value,
                                              void *        context);
static CFComparisonResult CFDeviceArrayComparatorFunction(const void *  val1,
                                                          const void *  val2,
                                                          void *        context);
static CFMutableDictionaryRef hu_CreateMatchingDictionary(uint32_t  inUsagePage,
                                                          uint32_t  inUsage);
 
// *****************************************************
#pragma mark - exported globals
// -----------------------------------------------------
 
IOHIDManagerRef gIOHIDManagerRef = NULL;
CFMutableArrayRef gDeviceCFArrayRef = NULL;
CFIndex gDeviceIndex;
CFArrayRef gElementCFArrayRef = NULL;
 
// *****************************************************
#pragma mark - local (static) globals
// -----------------------------------------------------
 
// *****************************************************
#pragma mark - exported function implementations
// -----------------------------------------------------
 
// *************************************************************************
//
// HIDBuildMultiDeviceList(inUsagePages, inUsages, inNumDeviceTypes)
//
// Purpose: builds list of devices with elements
//
// Inputs:  inUsagePages        - inNumDeviceTypes sized array of matching usage pages
// inUsages         - inNumDeviceTypes sized array of matching usages
// inNumDeviceTypes - number of usage pages & usages
//
// Returns: Boolean     - if successful
//
Boolean HIDBuildMultiDeviceList(const uint32_t *inUsagePages,
                                const uint32_t *inUsages,
                                int             inNumDeviceTypes) {
    Boolean result = false;                 // assume failure (pessimist!)
    Boolean first = (!gIOHIDManagerRef);    // not yet created?
 
    if (first) {
        // create the manager
        gIOHIDManagerRef = IOHIDManagerCreate(kCFAllocatorDefault,
                                              kIOHIDOptionsTypeNone);
    }
    if (gIOHIDManagerRef) {
        CFMutableArrayRef hidMatchingCFMutableArrayRef = NULL;
        if (inUsages &&
            inUsagePages &&
            inNumDeviceTypes)
        {
            hidMatchingCFMutableArrayRef = CFArrayCreateMutable(kCFAllocatorDefault,
                                                                0,
                                                                &kCFTypeArrayCallBacks);
            if (hidMatchingCFMutableArrayRef) {
                int idx;
                for (idx = 0; idx < inNumDeviceTypes; idx++) {  // for all usage and usage page types
                    // Set up matching dictionary. returns NULL on error.
                    CFMutableDictionaryRef hidMatchingCFDictRef = hu_CreateMatchingDictionary(inUsagePages[idx],
                                                                                              inUsages[idx]);
                    if (hidMatchingCFDictRef) {
                        CFArrayAppendValue(hidMatchingCFMutableArrayRef,
                                           (void *) hidMatchingCFDictRef);
                        CFRelease(hidMatchingCFDictRef);
                    } else {
                        fprintf(stderr,
                                "%s: Couldn’t create a matching dictionary.",
                                __PRETTY_FUNCTION__);
                    }
                }
            } else {
                        fprintf(stderr,
                        "%s: Couldn’t create a matching array.",
                        __PRETTY_FUNCTION__);
            }
        }
 
        // set it for IOHIDManager to use to match against
        IOHIDManagerSetDeviceMatchingMultiple(gIOHIDManagerRef,
                                              hidMatchingCFMutableArrayRef);
        if (hidMatchingCFMutableArrayRef) {
            CFRelease(hidMatchingCFMutableArrayRef);
        }
        if (first) {
            // open it
            IOReturn tIOReturn = IOHIDManagerOpen(gIOHIDManagerRef,
                                                  kIOHIDOptionsTypeNone);
            if (kIOReturnSuccess != tIOReturn) {
                fprintf(stderr,
                        "%s: Couldn’t open IOHIDManager.",
                        __PRETTY_FUNCTION__);
                goto Oops;
            }
        }
 
        HIDRebuildDevices();
        result = true;
    } else {
        fprintf(stderr,
                "%s: Couldn’t create a IOHIDManager.",
                __PRETTY_FUNCTION__);
    }
 
Oops:;
 
    return (result);
}       // HIDBuildMultiDeviceList
 
/*************************************************************************
 *
 * HIDBuildDeviceList(inUsagePage, inUsage)
 *
 * Purpose:  builds list of devices with elements
 *
 * Notes:   same as above but this uses a single inUsagePage and usage
 *          allocates memory and captures devices
 *          list is allocated internally within HID Utilites and can be accessed via accessor functions
 *          structures within list are considered flat and user accessable, but not user modifiable
 *          can be called again to rebuild list to account for new devices
 *          (will do the right thing in case of disposing existing list)
 *
 * Inputs:   inUsagePage        - usage page
 *          inUsage         - usages
 *
 * Returns:  Boolean        - if successful
 */
 
Boolean HIDBuildDeviceList(uint32_t inUsagePage,
                           uint32_t inUsage) {
    return (HIDBuildMultiDeviceList(&inUsagePage,
                                    &inUsage,
                                    1));    // call HIDBuildMultiDeviceList with a single usage
} // HIDBuildDeviceList
 
/*************************************************************************
 *
 * HIDUpdateDeviceList(inUsagePages, inUsages, inNumDeviceTypes)
 *
 * Purpose:  updates the current device list for any new/removed devices
 *
 * Notes:   if this is called before HIDBuildDeviceList then it functions like HIDBuildMultiDeviceList
 *          inUsagePage & inUsage are each a inNumDeviceTypes sized array of matching usage and usage pages
 *
 * Inputs:   inUsagePages       - inNumDeviceTypes sized array of matching usage pages
 *          inUsages            - inNumDeviceTypes sized array of matching usages
 *          inNumDeviceTypes - number of usage pages & usages
 *
 * Returns:  Boolean        - true if the device config changed
 */
 
Boolean HIDUpdateDeviceList(const uint32_t *inUsagePages,
                            const uint32_t *inUsages,
                            int             inNumDeviceTypes) {
    return (HIDBuildMultiDeviceList(inUsagePages,
                                    inUsages,
                                    inNumDeviceTypes));
} // HIDUpdateDeviceList
 
/*************************************************************************
 *
 * HIDReleaseDeviceList(void)
 *
 * Purpose:  release list built by above functions
 *
 * Notes:   MUST be called prior to application exit to properly release devices
 *          if not called(or app crashes) devices can be recovered by pluging into different location in USB chain
 *
 * Inputs:   none
 *
 * Returns:  none
 */
 
void HIDReleaseDeviceList(void) {
    if (gDeviceCFArrayRef) {
            CFRelease(gDeviceCFArrayRef);
        gDeviceCFArrayRef = NULL;
    }
}       // HIDReleaseDeviceList
 
/*************************************************************************
 *
 * HIDHaveDeviceList(void)
 *
 * Purpose:  does a device list exist?
 *
 * Inputs:   none
 *
 * Returns:  Boolean        - true if we have previously built a device list
 */
 
Boolean HIDHaveDeviceList(void) {
    return (NULL != gDeviceCFArrayRef);
}
 
// *************************************************************************
//
// HIDRebuildDevices()
//
// Purpose: rebuilds the (internal) list of IOHIDDevices
//
// Inputs:  none
//
// Returns: none
//
 
void HIDRebuildDevices(void) {
    // get the set of devices from the IOHID manager
    CFSetRef devCFSetRef = IOHIDManagerCopyDevices(gIOHIDManagerRef);
 
    if (devCFSetRef) {
        // if the existing array isn't empty...
        if (gDeviceCFArrayRef) {
            // release it
            CFRelease(gDeviceCFArrayRef);
        }
 
        // create an empty array
        gDeviceCFArrayRef = CFArrayCreateMutable(kCFAllocatorDefault,
                                                 0,
                                                 &kCFTypeArrayCallBacks);
        // now copy the set to the array
        CFSetApplyFunction(devCFSetRef,
                           CFSetApplierFunctionCopyToCFArray,
                           (void *) gDeviceCFArrayRef);
        // now sort the array by location ID's
        CFIndex cnt = CFArrayGetCount(gDeviceCFArrayRef);
        CFArraySortValues(gDeviceCFArrayRef,
                          CFRangeMake(0,
                                      cnt),
                          CFDeviceArrayComparatorFunction,
                          NULL);
 
        // and release the set we copied from the IOHID manager
        CFRelease(devCFSetRef);
    }
}       // HIDRebuildDevices
 
// ---------------------------------
 
// how many HID devices have been found
// returns 0 if no device list exist
 
CFIndex HIDCountDevices(void) {
    CFIndex result = 0;
 
    if (gDeviceCFArrayRef) {
        result = CFArrayGetCount(gDeviceCFArrayRef);
    }
 
    return (result);
}       // HIDCountDevices
 
// ---------------------------------
// how many elements does a specific device have
// returns 0 if device is invlaid or NULL
 
CFIndex HIDCountDeviceElements(IOHIDDeviceRef       inIOHIDDeviceRef,
                               HIDElementTypeMask   typeMask) {
    int count = 0;
 
    if (inIOHIDDeviceRef) {
        assert(IOHIDDeviceGetTypeID() == CFGetTypeID(inIOHIDDeviceRef));
 
        gElementCFArrayRef = IOHIDDeviceCopyMatchingElements(inIOHIDDeviceRef,
                                                             NULL,
                                                             kIOHIDOptionsTypeNone);
        if (gElementCFArrayRef) {
            CFIndex idx,
                    cnt = CFArrayGetCount(gElementCFArrayRef);
            for (idx = 0; idx < cnt; idx++) {
                IOHIDElementRef tIOHIDElementRef = (IOHIDElementRef) CFArrayGetValueAtIndex(gElementCFArrayRef,
                                                                                            idx);
                if (!tIOHIDElementRef) {
                    continue;
                }
 
                IOHIDElementType type = IOHIDElementGetType(tIOHIDElementRef);
 
                switch (type) {
                    case kIOHIDElementTypeInput_Misc:
                    case kIOHIDElementTypeInput_Button:
                    case kIOHIDElementTypeInput_Axis:
                    case kIOHIDElementTypeInput_ScanCodes:
                    {
                        if (typeMask &
                            kHIDElementTypeInput)
                        {
                            count++;
                        }
 
                        break;
                    }
 
                    case kIOHIDElementTypeOutput:
                    {
                        if (typeMask &
                            kHIDElementTypeOutput)
                        {
                            count++;
                        }
 
                        break;
                    }
 
                    case kIOHIDElementTypeFeature:
                    {
                        if (typeMask &
                            kHIDElementTypeFeature)
                        {
                            count++;
                        }
 
                        break;
                    }
 
                    case kIOHIDElementTypeCollection:
                    {
                        if (typeMask &
                            kHIDElementTypeCollection)
                        {
                            count++;
                        }
 
                        break;
                    }
 
                    default:
                    {
                        break;
                    }
                }   // switch (type)
            }       // next idx
 
            CFRelease(gElementCFArrayRef);
            gElementCFArrayRef = NULL;
        }   // if (gElementCFArrayRef)
    }       // if (inIOHIDDeviceRef)
 
    return (count);
}       /* HIDCountDeviceElements */
 
// ---------------------------------
// how many elements of a specifc type (or 0 for all types) does a specific device have?
// returns 0 if device is invlaid or NULL
 
CFIndex HIDCountDeviceElementsOfType(IOHIDDeviceRef     inIOHIDDeviceRef,
                                     IOHIDElementType   inIOHIDElementType) {
    int result = 0;
 
    if (inIOHIDDeviceRef) {
        assert(IOHIDDeviceGetTypeID() == CFGetTypeID(inIOHIDDeviceRef));
        CFDictionaryRef matchingDict = NULL;
        if (inIOHIDElementType) {
            const void *keys[] = {CFSTR(kIOHIDElementTypeKey)};
            const void *vals[] = {CFNumberCreate(kCFAllocatorDefault,
                                                 kCFNumberIntType,
                                                 &inIOHIDElementType)};
            matchingDict = CFDictionaryCreate(kCFAllocatorDefault,
                                              keys,
                                              vals,
                                              1,
                                              &kCFTypeDictionaryKeyCallBacks,
                                              &kCFTypeDictionaryValueCallBacks);
            CFRelease(vals[0]);
        }
 
        gElementCFArrayRef = IOHIDDeviceCopyMatchingElements(inIOHIDDeviceRef,
                                                             matchingDict,
                                                             kIOHIDOptionsTypeNone);
        if (gElementCFArrayRef) {
            CFIndex idx,
                    cnt = CFArrayGetCount(gElementCFArrayRef);
            for (idx = 0; idx < cnt; idx++) {
                IOHIDElementRef tIOHIDElementRef = (IOHIDElementRef) CFArrayGetValueAtIndex(gElementCFArrayRef,
                                                                                            idx);
                if (!tIOHIDElementRef) {
                    continue;
                }
 
                // HIDDumpElementInfo(tIOHIDElementRef);
                IOHIDElementType type = IOHIDElementGetType(tIOHIDElementRef);
                if (type == inIOHIDElementType) {
                    result++;
                }
            }   // next idx
 
            CFRelease(gElementCFArrayRef);
            gElementCFArrayRef = NULL;
        }   // if (gElementCFArrayRef)
        if (matchingDict) {
            CFRelease(matchingDict);
        }
    }   // if (inIOHIDDeviceRef)
 
    return (result);
}       /* HIDCountDeviceElementsOfType */
 
// ---------------------------------
 
// get the first device in the device list
// returns NULL if no list exists or it's empty
 
IOHIDDeviceRef HIDGetFirstDevice(void) {
    IOHIDDeviceRef result = NULL;
 
    gDeviceIndex = 0;
    if (gDeviceCFArrayRef) {
        CFIndex count = CFArrayGetCount(gDeviceCFArrayRef);
        if ((gDeviceIndex >= 0) &&
            (gDeviceIndex < count))
        {
            result = (IOHIDDeviceRef) CFArrayGetValueAtIndex(gDeviceCFArrayRef,
                                                             gDeviceIndex);
        }
    }
 
    return (result);
}       /* HIDGetFirstDevice */
 
// ---------------------------------
 
// get next device in list given current device as parameter
// returns NULL if end of list
 
IOHIDDeviceRef HIDGetNextDevice(IOHIDDeviceRef inIOHIDDeviceRef) {
    IOHIDDeviceRef result = NULL;
 
    if (gDeviceCFArrayRef &&
        inIOHIDDeviceRef)
    {
        CFIndex idx,
                cnt = CFArrayGetCount(gDeviceCFArrayRef);
        // quick case to verify the current device index is valid for current device
        if ((gDeviceIndex >= 0) &&
            (gDeviceIndex < cnt))
        {
            result = (IOHIDDeviceRef)         CFArrayGetValueAtIndex(gDeviceCFArrayRef,
                                                                     gDeviceIndex);
            if (result &&
                (result == inIOHIDDeviceRef))
            {
                result = NULL;
                gDeviceIndex++; // bump index
            } else {
                // previous index was invalid;
                gDeviceIndex = -1;
                // search for current device's index
                for (idx = 0; idx < cnt; idx++) {
                    result = (IOHIDDeviceRef) CFArrayGetValueAtIndex(gDeviceCFArrayRef,
                                                                     idx);
                    if ((result) &&
                        (result == inIOHIDDeviceRef))
                    {
                        gDeviceIndex = idx +
                                       1;   // found valid index; bump to next one
                        break;
                    }
                }
 
                result = NULL;
            }
            if ((gDeviceIndex >= 0) &&
                (gDeviceIndex < cnt))
            {
                result = (IOHIDDeviceRef)     CFArrayGetValueAtIndex(gDeviceCFArrayRef,
                                                                     gDeviceIndex);
            }
        }   // if valid index
    }       // if (gDeviceCFArrayRef && inIOHIDDeviceRef)
 
    return (result);
}       /* HIDGetNextDevice */
 
// ---------------------------------
 
// get the first element of device passed in as parameter
// returns NULL if no list exists or device does not exists or is NULL
IOHIDElementRef HIDGetFirstDeviceElement(IOHIDDeviceRef     inIOHIDDeviceRef,
                                         HIDElementTypeMask typeMask) {
    IOHIDElementRef result = NULL;
 
    if (inIOHIDDeviceRef) {
        assert(IOHIDDeviceGetTypeID() == CFGetTypeID(inIOHIDDeviceRef));
 
        gElementCFArrayRef = IOHIDDeviceCopyMatchingElements(inIOHIDDeviceRef,
                                                             NULL,
                                                             kIOHIDOptionsTypeNone);
        if (gElementCFArrayRef) {
            CFIndex idx,
                    cnt = CFArrayGetCount(gElementCFArrayRef);
            for (idx = 0; idx < cnt; idx++) {
                IOHIDElementRef tIOHIDElementRef = (IOHIDElementRef) CFArrayGetValueAtIndex(gElementCFArrayRef,
                                                                                            idx);
                if (!tIOHIDElementRef) {
                    continue;
                }
 
                IOHIDElementType type = IOHIDElementGetType(tIOHIDElementRef);
 
                switch (type) {
                    case kIOHIDElementTypeInput_Misc:
                    case kIOHIDElementTypeInput_Button:
                    case kIOHIDElementTypeInput_Axis:
                    case kIOHIDElementTypeInput_ScanCodes:
                    {
                        if (typeMask &
                            kHIDElementTypeInput)
                        {
                            result = tIOHIDElementRef;
                        }
 
                        break;
                    }
 
                    case kIOHIDElementTypeOutput:
                    {
                        if (typeMask &
                            kHIDElementTypeOutput)
                        {
                            result = tIOHIDElementRef;
                        }
 
                        break;
                    }
 
                    case kIOHIDElementTypeFeature:
                    {
                        if (typeMask &
                            kHIDElementTypeFeature)
                        {
                            result = tIOHIDElementRef;
                        }
 
                        break;
                    }
 
                    case kIOHIDElementTypeCollection:
                    {
                        if (typeMask &
                            kHIDElementTypeCollection)
                        {
                            result = tIOHIDElementRef;
                        }
 
                        break;
                    }
 
                    default:
                    {
                        break;
                    }
                }   // switch (type)
                if (result) {
                    break;  // DONE!
                }
            }   // next idx
 
            CFRelease(gElementCFArrayRef);
            gElementCFArrayRef = NULL;
        }   // if (gElementCFArrayRef)
    }       // if (inIOHIDDeviceRef)
 
    return (result);
}       /* HIDGetFirstDeviceElement */
 
// ---------------------------------
 
// get next element of given device in list given current element as parameter
// will walk down each collection then to next element or collection (depthwise traverse)
// returns NULL if end of list
// uses mask of HIDElementTypeMask to restrict element found
// use kHIDElementTypeIO to get previous HIDGetNextDeviceElement functionality
IOHIDElementRef HIDGetNextDeviceElement(IOHIDElementRef     inIOHIDElementRef,
                                        HIDElementTypeMask  typeMask) {
    IOHIDElementRef result = NULL;
 
    if (inIOHIDElementRef) {
        assert(IOHIDElementGetTypeID() == CFGetTypeID(inIOHIDElementRef));
 
        IOHIDDeviceRef tIOHIDDeviceRef = IOHIDElementGetDevice(inIOHIDElementRef);
        if (tIOHIDDeviceRef) {
            Boolean found = false;
 
            gElementCFArrayRef = IOHIDDeviceCopyMatchingElements(tIOHIDDeviceRef,
                                                                 NULL,
                                                                 kIOHIDOptionsTypeNone);
            if (gElementCFArrayRef) {
                CFIndex idx,
                        cnt = CFArrayGetCount(gElementCFArrayRef);
                for (idx = 0; idx < cnt; idx++) {
                    IOHIDElementRef tIOHIDElementRef = (IOHIDElementRef) CFArrayGetValueAtIndex(gElementCFArrayRef,
                                                                                                idx);
                    if (!tIOHIDElementRef) {
                        continue;
                    }
                    if (!found) {
                        if (inIOHIDElementRef == tIOHIDElementRef) {
                            found = true;
                        }
 
                        continue;   // next element
                    } else {
                        if (inIOHIDElementRef == tIOHIDElementRef) {
                            continue;   // next element
                        }
 
                        // we've found the current element; now find the next one of the right type
                        IOHIDElementType type = IOHIDElementGetType(tIOHIDElementRef);
 
                        switch (type) {
                            case kIOHIDElementTypeInput_Misc:
                            case kIOHIDElementTypeInput_Button:
                            case kIOHIDElementTypeInput_Axis:
                            case kIOHIDElementTypeInput_ScanCodes:
                            {
                                if (typeMask &
                                    kHIDElementTypeInput)
                                {
                                    result = tIOHIDElementRef;
                                }
 
                                break;
                            }
 
                            case kIOHIDElementTypeOutput:
                            {
                                if (typeMask &
                                    kHIDElementTypeOutput)
                                {
                                    result = tIOHIDElementRef;
                                }
 
                                break;
                            }
 
                            case kIOHIDElementTypeFeature:
                            {
                                if (typeMask &
                                    kHIDElementTypeFeature)
                                {
                                    result = tIOHIDElementRef;
                                }
 
                                break;
                            }
 
                            case kIOHIDElementTypeCollection:
                            {
                                if (typeMask &
                                    kHIDElementTypeCollection)
                                {
                                    result = tIOHIDElementRef;
                                }
 
                                break;
                            }
 
                            default:
                            {
                                break;
                            }
                        }   // switch (type)
                        if (result) {
                            break;  // DONE!
                        }
                    }   // if (!found)
                }       // next idx
 
                CFRelease(gElementCFArrayRef);
                gElementCFArrayRef = NULL;
            }   // if (gElementCFArrayRef)
        }       // if (inIOHIDDeviceRef)
    }           // if (inIOHIDElementRef)
 
    return (result);
}       /* HIDGetNextDeviceElement */
 
// utility routine to dump device info
void HIDDumpDeviceInfo(IOHIDDeviceRef inIOHIDDeviceRef) {
    char cstring[256];
 
    printf("Device: %p = { ",
           inIOHIDDeviceRef);
 
    char manufacturer[256] = "";    // name of manufacturer
    CFStringRef tCFStringRef = IOHIDDevice_GetManufacturer(inIOHIDDeviceRef);
    if (tCFStringRef) {
        verify(CFStringGetCString(tCFStringRef,
                                  manufacturer,
                                  sizeof(manufacturer),
                                  kCFStringEncodingUTF8));
    }
 
    char product[256] = ""; // name of product
    tCFStringRef = IOHIDDevice_GetProduct(inIOHIDDeviceRef);
    if (tCFStringRef) {
        verify(CFStringGetCString(tCFStringRef,
                                  product,
                                  sizeof(product),
                                  kCFStringEncodingUTF8));
    }
 
            printf("%s - %s, ",
           manufacturer,
           product);
 
    uint32_t vendorID = IOHIDDevice_GetVendorID(inIOHIDDeviceRef);
    if (vendorID) {
#if true
            printf("    vendorID:   0x%04X, ",
               vendorID);
#else   // if 1
        if (HIDGetVendorNameFromVendorID(vendorID,
                                         cstring))
        {
            printf("    vendorID:   0x%04lX (\"%s\"), ",
                   vendorID,
                   cstring);
        } else {
            printf("    vendorID:   0x%04lX, ",
                   vendorID);
        }
 
#endif  // if 1
    }
 
    uint32_t productID = IOHIDDevice_GetProductID(inIOHIDDeviceRef);
    if (productID) {
#if true
            printf("    productID:  0x%04X, ",
               productID);
#else   // if 1
        if (HIDGetProductNameFromVendorProductID(vendorID,
                                                 productID,
                                                 cstring))
        {
            printf("    productID:  0x%04lX (\"%s\"), ",
                   productID,
                   cstring);
        } else {
            printf("    productID:  0x%04lX, ",
                   productID);
        }
 
#endif  // if 1
    }
 
    uint32_t usagePage = IOHIDDevice_GetUsagePage(inIOHIDDeviceRef);
    uint32_t usage = IOHIDDevice_GetUsage(inIOHIDDeviceRef);
    if (!usagePage ||
        !usage)
    {
        usagePage = IOHIDDevice_GetPrimaryUsagePage(inIOHIDDeviceRef);
        usage = IOHIDDevice_GetPrimaryUsage(inIOHIDDeviceRef);
    }
 
            printf("usage: 0x%04X:0x%04X, ",
           usagePage,
           usage);
 
#if true
    tCFStringRef = HIDCopyUsageName(usagePage,
                                    usage);
    if (tCFStringRef) {
        verify(CFStringGetCString(tCFStringRef,
                                  cstring,
                                  sizeof(cstring),
                                  kCFStringEncodingUTF8));
        printf("\"%s\", ",
               cstring);
        CFRelease(tCFStringRef);
    }
 
#endif  // if 1
 
#if true
    tCFStringRef = IOHIDDevice_GetTransport(inIOHIDDeviceRef);
    if (tCFStringRef) {
        verify(CFStringGetCString(tCFStringRef,
                                  cstring,
                                  sizeof(cstring),
                                  kCFStringEncodingUTF8));
        printf("Transport: \"%s\", ",
               cstring);
    }
 
    uint32_t vendorIDSource = IOHIDDevice_GetVendorIDSource(inIOHIDDeviceRef);
    if (vendorIDSource) {
        printf("VendorIDSource: %u, ",
               vendorIDSource);
    }
 
    uint32_t version = IOHIDDevice_GetVersionNumber(inIOHIDDeviceRef);
    if (version) {
        printf("version: %u, ",
               version);
    }
 
    tCFStringRef = IOHIDDevice_GetSerialNumber(inIOHIDDeviceRef);
    if (tCFStringRef) {
        verify(CFStringGetCString(tCFStringRef,
                                  cstring,
                                  sizeof(cstring),
                                  kCFStringEncodingUTF8));
        printf("SerialNumber: \"%s\", ",
               cstring);
    }
 
    uint32_t country = IOHIDDevice_GetCountryCode(inIOHIDDeviceRef);
    if (country) {
        printf("CountryCode: %u, ",
               country);
    }
 
    uint32_t locationID = IOHIDDevice_GetLocationID(inIOHIDDeviceRef);
    if (locationID) {
        printf("locationID: 0x%08X, ",
               locationID);
    }
 
#if false
    CFArrayRef pairs = IOHIDDevice_GetUsagePairs(inIOHIDDeviceRef);
    if (pairs) {
        CFIndex idx,
                cnt = CFArrayGetCount(pairs);
        for (idx = 0; idx < cnt; idx++) {
            const void *pair = CFArrayGetValueAtIndex(pairs,
                                                      idx);
            CFShow(pair);
        }
    }
 
#endif  // if false
    uint32_t maxInputReportSize = IOHIDDevice_GetMaxInputReportSize(inIOHIDDeviceRef);
    if (maxInputReportSize) {
        printf("MaxInputReportSize: %u, ",
               maxInputReportSize);
    }
 
    uint32_t maxOutputReportSize = IOHIDDevice_GetMaxOutputReportSize(inIOHIDDeviceRef);
    if (maxOutputReportSize) {
        printf("MaxOutputReportSize: %u, ",
               maxOutputReportSize);
    }
 
    uint32_t maxFeatureReportSize = IOHIDDevice_GetMaxFeatureReportSize(inIOHIDDeviceRef);
    if (maxFeatureReportSize) {
        printf("MaxFeatureReportSize: %u, ",
               maxOutputReportSize);
    }
 
    uint32_t reportInterval = IOHIDDevice_GetReportInterval(inIOHIDDeviceRef);
    if (reportInterval) {
        printf("ReportInterval: %u, ",
               reportInterval);
    }
 
    IOHIDQueueRef queueRef = IOHIDDevice_GetQueue(inIOHIDDeviceRef);
    if (queueRef) {
        printf("queue: %p, ",
               queueRef);
    }
 
    IOHIDTransactionRef transactionRef = IOHIDDevice_GetTransaction(inIOHIDDeviceRef);
    if (transactionRef) {
        printf("transaction: %p, ",
               transactionRef);
    }
 
#endif  // if 1
        printf("}\n");
    fflush(stdout);
}       // HIDDumpDeviceInfo
 
// utility routine to dump element info
void HIDDumpElementInfo(IOHIDElementRef inIOHIDElementRef) {
    if (inIOHIDElementRef) {
        printf("    Element: %p = { ",
               inIOHIDElementRef);
#if false   // enable to printf parent
        IOHIDDeviceRef tIOHIDDeviceRef = IOHIDElementGetDevice(inIOHIDElementRef);
        printf("Device: %p, ",
               tIOHIDDeviceRef);
#endif  // if 0
        IOHIDElementRef parentIOHIDElementRef = IOHIDElementGetParent(inIOHIDElementRef);
        printf("parent: %p, ",
               parentIOHIDElementRef);
#if false   // enable to printf children
        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%08X, ",
                (uint32_t) 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%04x:0x%04x, ",
               usagePage,
               usage);
#if true
        CFStringRef tCFStringRef = HIDCopyUsageName(usagePage,
                                                    usage);
        if (tCFStringRef) {
            char usageString[256] = "";
            verify(CFStringGetCString(tCFStringRef,
                                      usageString,
                                      sizeof(usageString),
                                      kCFStringEncodingUTF8));
            printf("\"%s\", ",
                   usageString);
            CFRelease(tCFStringRef);
        }
 
#endif // if true
        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: %u, Size: %u, Count: %u }, ",
               reportID,
               reportSize,
               reportCount);
 
        uint32_t unit = IOHIDElementGetUnit(inIOHIDElementRef);
        uint32_t unitExp = IOHIDElementGetUnitExponent(inIOHIDElementRef);
        if (unit ||
            unitExp)
        {
            printf("unit: %u * 10^%u, ",
                   unit,
                   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
 
void HIDDumpElementCalibrationInfo(IOHIDElementRef inIOHIDElementRef) {
            printf("    Element: %p = { ",
           inIOHIDElementRef);
 
    CFIndex calMin = IOHIDElement_GetCalibrationMin(inIOHIDElementRef);
    CFIndex calMax = IOHIDElement_GetCalibrationMax(inIOHIDElementRef);
            printf("cal: {min: %ld, max: %ld}, ",
           calMin,
           calMax);
 
    CFIndex satMin = IOHIDElement_GetCalibrationSaturationMin(inIOHIDElementRef);
    CFIndex satMax = IOHIDElement_GetCalibrationSaturationMax(inIOHIDElementRef);
            printf("sat: {min: %ld, max: %ld}, ",
           satMin,
           satMax);
 
    CFIndex deadMin = IOHIDElement_GetCalibrationDeadZoneMin(inIOHIDElementRef);
    CFIndex deadMax = IOHIDElement_GetCalibrationDeadZoneMax(inIOHIDElementRef);
            printf("dead: {min: %ld, max: %ld}, ",
           deadMin,
           deadMax);
 
    double_t granularity = IOHIDElement_GetCalibrationGranularity(inIOHIDElementRef);
            printf("granularity: %6.2f }\n",
           granularity);
}       // HIDDumpElementCalibrationInfo
 
// *****************************************************
#pragma mark - local (static) function implementations
// -----------------------------------------------------
 
// *************************************************************************
//
// CFSetApplierFunctionCopyToCFArray(value, context)
//
// Purpose: CFSetApplierFunction to copy the CFSet to a CFArray
//
// Notes:   called one time for each item in the CFSet
//
// Inputs:  value           - the current element of the CFSet
// context          - the CFMutableArrayRef we're adding the CFSet elements to
//
// Returns: nothing
//
static void CFSetApplierFunctionCopyToCFArray(const void *  value,
                                              void *        context) {
    // printf("%s: 0x%08lX\n", __PRETTY_FUNCTION__, (long unsigned int) value);
    CFArrayAppendValue((CFMutableArrayRef) context,
                       value);
}       // CFSetApplierFunctionCopyToCFArray
 
// ---------------------------------
// used to sort the CFDevice array after copying it from the (unordered) (CF)set.
// we compare based on the location ID's since they're consistant (across boots & launches).
//
static CFComparisonResult CFDeviceArrayComparatorFunction(const void *  val1,
                                                          const void *  val2,
                                                          void *        context) {
#pragma unused(context)
    CFComparisonResult result = kCFCompareEqualTo;
 
    uint32_t loc1 = IOHIDDevice_GetLocationID((IOHIDDeviceRef) val1);
    uint32_t loc2 = IOHIDDevice_GetLocationID((IOHIDDeviceRef) val2);
    if (loc1 < loc2) {
        result = kCFCompareLessThan;
    } else if (loc1 > loc2) {
        result = kCFCompareGreaterThan;
    }
 
    return (result);
}       // CFDeviceArrayComparatorFunction
 
// *************************************************************************
//
// hu_CreateMatchingDictionary(inUsagePage, inUsage)
//
// Purpose: builds a matching dictionary based on usage page and usage
//
// Notes:   Only called by HIDBuildMultiDeviceList
//
// Inputs:  inUsagePage             - usage page
// inUsage                  - usages
//
// Returns: CFMutableDictionaryRef  - the matching dictionary
//
 
static CFMutableDictionaryRef hu_CreateMatchingDictionary(uint32_t  inUsagePage,
                                                          uint32_t  inUsage) {
    // create a dictionary to add usage page/usages to
    CFMutableDictionaryRef refHIDMatchDictionary = CFDictionaryCreateMutable(kCFAllocatorDefault,
                                                                             0,
                                                                             &kCFTypeDictionaryKeyCallBacks,
                                                                             &kCFTypeDictionaryValueCallBacks);
 
    if (refHIDMatchDictionary) {
        if (inUsagePage) {
            // Add key for device type to refine the matching dictionary.
            CFNumberRef pageCFNumberRef = CFNumberCreate(kCFAllocatorDefault,
                                                         kCFNumberSInt32Type,
                                                         &inUsagePage);
            if (pageCFNumberRef) {
                CFDictionarySetValue(refHIDMatchDictionary,
                                     CFSTR(kIOHIDPrimaryUsagePageKey),
                                     pageCFNumberRef);
                CFRelease(pageCFNumberRef);
                // note: the usage is only valid if the usage page is also defined
                if (inUsage) {
                    CFNumberRef usageCFNumberRef = CFNumberCreate(kCFAllocatorDefault,
                                                                  kCFNumberSInt32Type,
                                                                  &inUsage);
                    if (usageCFNumberRef) {
                        CFDictionarySetValue(refHIDMatchDictionary,
                                             CFSTR(kIOHIDPrimaryUsageKey),
                                             usageCFNumberRef);
                        CFRelease(usageCFNumberRef);
                    } else {
                        fprintf(stderr,
                                "%s: CFNumberCreate(usage) failed.",
                                __PRETTY_FUNCTION__);
                    }
                }
            } else {
                        fprintf(stderr,
                        "%s: CFNumberCreate(usage page) failed.",
                        __PRETTY_FUNCTION__);
            }
        }
    } else {
                        fprintf(stderr,
                "%s: CFDictionaryCreateMutable failed.",
                __PRETTY_FUNCTION__);
    }
 
    return (refHIDMatchDictionary);
}       // hu_CreateMatchingDictionary