Difference Analysis Generated by HtmlDiff on 10/26/2004 1:35 PM  

Base file: C:\CY4632_RDK_1_21\Software\Source Code\CUSBHidAPI\HidAPI.cpp

Modified file: C:\CY4632_RDK_1_3\Software\Source Code\CUSBHidAPI\HidAPI.cpp

//--------------------------------------------------------------------------
//
// USB HID Device and USB HID Device Manager Helper Classes
//
// The building blocks for this code was derived from the HCLIENT sample code
// provided by Microsoft in the Windows XP DDK.  
//
//--------------------------------------------------------------------------
// $Archive: /WirelessUSB/WUSB Kits/CY4632 LS KBM RDK/DocSrc/CD_Root/Software/Source Code/CUSBHidAPI/HidAPI.cpp $
// $Modtime: 2/16/04 4:54p9/14/04 9:07a $
// $Revision: 34 $
//--------------------------------------------------------------------------
//
// Copyright 2003-2004, Cypress Semiconductor Corporation.
//
// This software is owned by Cypress Semiconductor Corporation (Cypress)
// and is protected by and subject to worldwide patent protection (United
// States and foreign), United States copyright laws and international
// treaty provisions. Cypress hereby grants to licensee a personal,
// non-exclusive, non-transferable license to copy, use, modify, create
// derivative works of, and compile the Cypress Source Code and derivative
// works for the sole purpose of creating custom software in support of
// licensee product to be used only in conjunction with a Cypress integrated
// circuit as specified in the applicable agreement. Any reproduction,
// modification, translation, compilation, or representation of this
// software except as specified above is prohibited without the express
// written permission of Cypress.
//
// Disclaimer: CYPRESS MAKES NO WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// WITH REGARD TO THIS MATERIAL, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
// Cypress reserves the right to make changes without further notice to the
// materials described herein. Cypress does not assume any liability arising
// out of the application or use of any product or circuit described herein.
// Cypress does not authorize its products for use as critical components in
// life-support systems where a malfunction or failure may reasonably be
// expected to result in significant injury to the user. The inclusion of
// Cypress’ product in a life-support systems application implies that the
// manufacturer assumes all risk of such use and in doing so indemnifies
// Cypress against all charges.
//
// Use may be limited by and subject to the applicable Cypress software
// license agreement.
//
//--------------------------------------------------------------------------
#include "StdAfx.h"
#include <dbt.h>

#include ".\hidapi.h"


#define HIDMANAGER_CLASS _T("CHidManager")
CHidManager* CHidManager::m_pThis = NULL;

OSVERSIONINFO m_OS;

bool CHidDevice::InitializeHidDevice(PCHAR pDevicePath, USHORT uID)
{
    nextHidDevice = NULL;

    m_bOpened = false;

    m_hDevice = NULL;
    m_hDeviceRegistered = NULL;
    m_hWnd = NULL;

    m_bOpenedForRead = false;
    m_bOpenedForWrite = false;
    m_bOpenedOverlapped = false;
    m_bOpenedExclusive = false;

    m_uDeviceID = uID;

    m_pHidDevicePath = NULL;
    m_pHidPpd = NULL;
    ::ZeroMemory(&m_HidCaps, sizeof(m_HidCaps));
    ::ZeroMemory(&m_HidAttributes, sizeof(m_HidAttributes));

    m_pInputReportBuffer = NULL;
    m_pInputData = NULL;
    m_ulInputDataLength = 0;
    m_pInputButtonCaps = NULL;
    m_pInputValueCaps = NULL;

    m_pOutputReportBuffer = NULL;
    m_pOutputData = NULL;
    m_ulOutputDataLength = 0;
    m_pOutputButtonCaps = NULL;
    m_pOutputValueCaps = NULL;

    m_pFeatureReportBuffer = NULL;
    m_pFeatureData = NULL;
    m_ulFeatureDataLength = 0;
    m_pFeatureButtonCaps = NULL;
    m_pFeatureValueCaps = NULL;

    m_pHidDevicePath = new CHAR[lstrlen(pDevicePath)+1];
    if (!m_pHidDevicePath)
        return false;

    ::strcpy(m_pHidDevicePath, pDevicePath);

    return true;
}

void CHidDevice::DestroyHidDevice()
{
    if (m_bOpened)
    {
        CloseHidDevice();
    }

    if (m_pHidDevicePath)
        delete [] m_pHidDevicePath;
    
    m_uDeviceID = 0xFFFF;
}

// sets appropriate access rights, attempts to open a 
// handle to the HID device, obtains the top collection data, and 
// makes a call to setup input, output, and feature data buffers. 
bool CHidDevice::OpenHidDevice( bool bReadAccess, 
                                bool bWriteAccess, 
                                bool bUseOverlapped, 
                                bool bExclusive)
{
    DWORD   accessFlags = 0;
    DWORD   sharingFlags = 0;

    // Check for valid device path
    if (!m_pHidDevicePath) 
    {
        return false;
    }

    // setup access flag values
    if (bReadAccess)
    {
        accessFlags |= GENERIC_READ;
    }

    if (bWriteAccess)
    {
        accessFlags |= GENERIC_WRITE;
    }

    if (!bExclusive)
    {
        sharingFlags = FILE_SHARE_READ | FILE_SHARE_WRITE;
    }
    
    //
    //  The hid.dll api's do not pass the overlapped structure into deviceiocontrol
    //  so to use them we must have a non overlapped device.  If the request is for
    //  an overlapped device we will close the device below and get a handle to an
    //  overlapped device
    //
    
    m_hDevice = ::CreateFile (m_pHidDevicePath,
                              accessFlags,
                              sharingFlags,
                              NULL,        // no SECURITY_ATTRIBUTES structure
                              OPEN_EXISTING, // No special create flags
                              0,   // Open device as non-overlapped so we can get data
                              NULL);       // No template file

    if (m_hDevice == INVALID_HANDLE_VALUE) 
    {
        return false;
    }

    // open was successful
    m_bOpened = true;

    // set access booleans appropriately
    m_bOpenedForRead = bReadAccess;
    m_bOpenedForWrite = bWriteAccess;
    m_bOpenedOverlapped = bUseOverlapped;
    m_bOpenedExclusive = bExclusive;
    
    // obtain the top level collection's preparsed data from the HID device.  
    // the top level collection is a HID collection that is not nested with 
    // other collections.  a HID collection is a meaningful group of HID controls
    // and their respective HID usages.
    if (!::HidD_GetPreparsedData (m_hDevice, &m_pHidPpd)) 
    {
        // if failure, close HID handle and return failure
        CloseHidDevice();
        return false;
    }

    // get the attributes of the top level collection
    if (!::HidD_GetAttributes (m_hDevice, &m_HidAttributes)) 
    {
        // if failure, close HID handle, free preparsed data
        // and return failure
        ::HidD_FreePreparsedData (m_pHidPpd);
        m_pHidPpd = NULL;

        CloseHidDevice();
        return false;
    }

    // get the top level collections capability
    if (!::HidP_GetCaps (m_pHidPpd, &m_HidCaps))
    {
        // if failure, close HID handle, free preparsed data
        // and return failure
        ::HidD_FreePreparsedData (m_pHidPpd);
        m_pHidPpd = NULL;

        CloseHidDevice();
        return false;
    }

    // attempt to setup the HID device
    if (!SetupHidDevice())
    {
        // if failure, close HID handle, free preparsed data
        // and return failure
        ::HidD_FreePreparsedData (m_pHidPpd);
        m_pHidPpd = NULL;

        CloseHidDevice();
        return false;
    }

    //
    // At this point the client has a choice.  It may chose to look at the
    // Usage and Page of the top level collection found in the HIDP_CAPS
    // structure.  In this way it could just use the usages it knows about.
    // If either HidP_GetUsages or HidP_GetUsageValue return an error then
    // that particular usage does not exist in the report.
    //

    // verify that this is a HID device we care about
    if (!VerifyHidDevice())
    {
        // if failure, close HID handle, free preparsed data
        // and return failure
        ::HidD_FreePreparsedData (m_pHidPpd);
        m_pHidPpd = NULL;

        CloseHidDevice();
        return false;
    }

    // if overlapped I/O requested, close handle and reopen using overlapped I/O
    if (bUseOverlapped)
    {
        ::CloseHandle(m_hDevice);

        m_hDevice = ::CreateFile (m_pHidDevicePath,
                                  accessFlags,
                                  sharingFlags,
                                  NULL,        // no SECURITY_ATTRIBUTES structure
                                  OPEN_EXISTING, // No special create flags
                                  FILE_FLAG_OVERLAPPED, // Now we open the device as overlapped
                                  NULL);       // No template file
    
        if (m_hDevice == INVALID_HANDLE_VALUE) 
        {
            // if failure, close HID handle, free preparsed data
            // and return failure
            ::HidD_FreePreparsedData (m_pHidPpd);
            m_pHidPpd = NULL;

            CloseHidDevice();
            return false;
        }
    }

    return true;
}

// registers the HID device handle for event notification, mainly to get
// notified when the device is removed
bool CHidDevice::RegisterHidDevice(HWND hWnd)
{
    DEV_BROADCAST_HANDLE broadcastHandle;

    broadcastHandle.dbch_size = sizeof(DEV_BROADCAST_HANDLE);
    broadcastHandle.dbch_devicetype = DBT_DEVTYP_HANDLE;
    broadcastHandle.dbch_handle = m_hDevice;
   
    m_hDeviceRegistered = ::RegisterDeviceNotification(hWnd, (PVOID) &broadcastHandle, DEVICE_NOTIFY_WINDOW_HANDLE);
    if (!m_hDeviceRegistered) 
        return false;

    m_hWnd = hWnd;

    return true;
}

// sets up HID input, output and feature data buffers 
// used to simplify communication with HID devices
bool CHidDevice::SetupHidDevice()
{
    USHORT              numCaps;
    ULONG               i;
    USAGE               usage;

    PHIDP_VALUE_CAPS valueCaps;
    PHIDP_BUTTON_CAPS buttonCaps;
    PHID_DATA data;
    USHORT numValues;

    //
    // setup Input Data buffers.
    //

    //
    // Allocate memory to hold on input report
    //

    if (m_HidCaps.InputReportByteLength)
    {
        m_pInputReportBuffer = new CHAR[m_HidCaps.InputReportByteLength];
        if (m_pInputReportBuffer)
            ZeroMemory (m_pInputReportBuffer, m_HidCaps.InputReportByteLength);
    }

    //
    // Allocate memory to hold the button and value capabilities.
    // NumberXXCaps is in terms of array elements.
    //
    
    if (m_HidCaps.NumberInputButtonCaps)
    {
        m_pInputButtonCaps = new HIDP_BUTTON_CAPS[m_HidCaps.NumberInputButtonCaps];
        if (!m_pInputButtonCaps)
            return false;

        ZeroMemory (m_pInputButtonCaps, m_HidCaps.NumberInputButtonCaps * sizeof(HIDP_BUTTON_CAPS));

        numCaps = m_HidCaps.NumberInputButtonCaps;
        ::HidP_GetButtonCaps (HidP_Input,
                            m_pInputButtonCaps,
                            &numCaps,
                            m_pHidPpd);
    }

    if (m_HidCaps.NumberInputValueCaps)
    {
        m_pInputValueCaps = new HIDP_VALUE_CAPS[m_HidCaps.NumberInputValueCaps];
        if (!m_pInputValueCaps)
            return false;

        ZeroMemory (m_pInputValueCaps, m_HidCaps.NumberInputValueCaps * sizeof(HIDP_VALUE_CAPS));

        numCaps = m_HidCaps.NumberInputValueCaps;
        ::HidP_GetValueCaps (HidP_Input,
                            m_pInputValueCaps,
                            &numCaps,
                            m_pHidPpd);
    }

    //
    // Depending on the device, some value caps structures may represent more
    // than one value.  (A range).  In the interest of being verbose, over
    // efficient, we will expand these so that we have one and only one
    // struct _HID_DATA for each value.
    //
    // To do this we need to count up the total number of values are listed
    // in the value caps structure.  For each element in the array we test
    // for range if it is a range then UsageMax and UsageMin describe the
    // usages for this range INCLUSIVE.
    //

    valueCaps = m_pInputValueCaps;
    numValues = 0;

    if (valueCaps)
    {
        for (i = 0; i < m_HidCaps.NumberInputValueCaps; i++, valueCaps++) 
        {
            if (valueCaps->IsRange) 
            {
                numValues += valueCaps->Range.UsageMax - valueCaps->Range.UsageMin + 1;
            }
            else
            {
                numValues++;
            }
        }
    }

    //
    // Allocate a buffer to hold the struct _HID_DATA structures.
    // One element for each set of buttons, and one element for each value
    // found.
    //

    m_ulInputDataLength = m_HidCaps.NumberInputButtonCaps + numValues;

    if (m_ulInputDataLength)
    {
        m_pInputData = new HID_DATA[m_ulInputDataLength];
        if (!m_pInputData)
            return false;

        ZeroMemory (m_pInputData, m_ulInputDataLength * sizeof(HID_DATA));
    }

    //
    // Fill in the button data
    //

    buttonCaps = m_pInputButtonCaps;
    data = m_pInputData;

    if (buttonCaps && data)
    {
        for (i = 0;
            i < m_HidCaps.NumberInputButtonCaps;
            i++, data++, buttonCaps++) 
        {
            data->IsButtonData = true;
            data->Status = HIDP_STATUS_SUCCESS;
            data->UsagePage = buttonCaps->UsagePage;
            if (buttonCaps->IsRange) 
            {
                data->ButtonData.UsageMin = buttonCaps->Range.UsageMin;
                data->ButtonData.UsageMax = buttonCaps->Range.UsageMax;
            }
            else
            {
                data->ButtonData.UsageMin = data->ButtonData.UsageMax = buttonCaps->NotRange.Usage;
            }
            
            data->ButtonData.MaxUsageLength = ::HidP_MaxUsageListLength (
                                                    HidP_Input,
                                                    buttonCaps->UsagePage,
                                                    m_pHidPpd);
        
            data->ButtonData.Usages = new USAGE[data->ButtonData.MaxUsageLength];
            ZeroMemory (data->ButtonData.Usages, data->ButtonData.MaxUsageLength * sizeof(USAGE));

            data->ReportID = buttonCaps->ReportID;
        }
    }

    //
    // Fill in the value data
    //

    valueCaps = m_pInputValueCaps;

    if (valueCaps)
    {
        for (i = 0; i < numValues; i++, valueCaps++)
        {
            if (valueCaps->IsRange) 
            {
                for (usage = valueCaps->Range.UsageMin;
                    usage <= valueCaps->Range.UsageMax;
                    usage++) 
                {
                    data->IsButtonData = false;
                    data->Status = HIDP_STATUS_SUCCESS;
                    data->UsagePage = valueCaps->UsagePage;
                    data->ValueData.Usage = usage;
                    data->ReportID = valueCaps->ReportID;
                    data++;
                }
            } 
            else
            {
                data->IsButtonData = false;
                data->Status = HIDP_STATUS_SUCCESS;
                data->UsagePage = valueCaps->UsagePage;
                data->ValueData.Usage = valueCaps->NotRange.Usage;
                data->ReportID = valueCaps->ReportID;
                data++;
            }
        }
    }

    //
    // setup Output Data buffers.
    //

    if (m_HidCaps.OutputReportByteLength)
    {
        m_pOutputReportBuffer = new CHAR[m_HidCaps.OutputReportByteLength];
        if (m_pOutputReportBuffer)
            ZeroMemory (m_pOutputReportBuffer, m_HidCaps.OutputReportByteLength);
    }

    if (m_HidCaps.NumberOutputButtonCaps)
    {
        m_pOutputButtonCaps = new HIDP_BUTTON_CAPS[m_HidCaps.NumberOutputButtonCaps];
        if (!m_pOutputButtonCaps)
            return false;

        ZeroMemory (m_pOutputButtonCaps, m_HidCaps.NumberOutputButtonCaps * sizeof(HIDP_BUTTON_CAPS));

        numCaps = m_HidCaps.NumberOutputButtonCaps;
        ::HidP_GetButtonCaps (HidP_Output,
                            m_pOutputButtonCaps,
                            &numCaps,
                            m_pHidPpd);
    }

    if (m_HidCaps.NumberOutputValueCaps)
    {
        m_pOutputValueCaps = new HIDP_VALUE_CAPS[m_HidCaps.NumberOutputValueCaps];
        if (!m_pOutputValueCaps)
            return false;

        ZeroMemory (m_pOutputValueCaps, m_HidCaps.NumberOutputValueCaps * sizeof(HIDP_VALUE_CAPS));

        numCaps = m_HidCaps.NumberOutputValueCaps;
        ::HidP_GetValueCaps (HidP_Output,
                            m_pOutputValueCaps,
                            &numCaps,
                            m_pHidPpd);
    }

    valueCaps = m_pOutputValueCaps;
    numValues = 0;

    if (valueCaps)
    {
        for (i = 0; i < m_HidCaps.NumberOutputValueCaps; i++, valueCaps++) 
        {
            if (valueCaps->IsRange) 
            {
                numValues += valueCaps->Range.UsageMax
                        - valueCaps->Range.UsageMin + 1;
            } 
            else
            {
                numValues++;
            }
        }
    }

    m_ulOutputDataLength = m_HidCaps.NumberOutputButtonCaps + numValues;

    if (m_ulOutputDataLength)
    {
        m_pOutputData = new HID_DATA[m_ulOutputDataLength];
        if (!m_pOutputData)
            return false;

        ZeroMemory (m_pOutputData, m_ulOutputDataLength * sizeof(HID_DATA));
    }

    buttonCaps = m_pOutputButtonCaps;
    data = m_pOutputData;

    if (buttonCaps && data)
    {
        for (i = 0;
            i < m_HidCaps.NumberOutputButtonCaps;
            i++, data++, buttonCaps++) 
        {
            data->IsButtonData = true;
            data->Status = HIDP_STATUS_SUCCESS;
            data->UsagePage = buttonCaps->UsagePage;

            if (buttonCaps->IsRange)
            {
                data->ButtonData.UsageMin = buttonCaps->Range.UsageMin;
                data->ButtonData.UsageMax = buttonCaps->Range.UsageMax;
            }
            else
            {
                data->ButtonData.UsageMin = data->ButtonData.UsageMax = buttonCaps->NotRange.Usage;
            }

            data->ButtonData.MaxUsageLength = ::HidP_MaxUsageListLength (
                                                        HidP_Output,
                                                        buttonCaps->UsagePage,
                                                        m_pHidPpd);

            data->ButtonData.Usages = new USAGE[data->ButtonData.MaxUsageLength];
            ZeroMemory (data->ButtonData.Usages, data->ButtonData.MaxUsageLength * sizeof(USAGE));

            data->ReportID = buttonCaps->ReportID;
        }
    }

    valueCaps = m_pOutputValueCaps;

    if (valueCaps)
    {
        for (i = 0; i < numValues; i++, valueCaps++)
        {
            if (valueCaps->IsRange)
            {
                for (usage = valueCaps->Range.UsageMin;
                    usage <= valueCaps->Range.UsageMax;
                    usage++) 
                {
                    data->IsButtonData = false;
                    data->Status = HIDP_STATUS_SUCCESS;
                    data->UsagePage = valueCaps->UsagePage;
                    data->ValueData.Usage = usage;
                    data->ReportID = valueCaps->ReportID;
                    data++;
                }
            }
            else
            {
                data->IsButtonData = false;
                data->Status = HIDP_STATUS_SUCCESS;
                data->UsagePage = valueCaps->UsagePage;
                data->ValueData.Usage = valueCaps->NotRange.Usage;
                data->ReportID = valueCaps->ReportID;
                data++;
            }
        }
    }

    //
    // setup Feature Data buffers.
    //

    if (m_HidCaps.FeatureReportByteLength)
    {
        m_pFeatureReportBuffer = new CHAR[m_HidCaps.FeatureReportByteLength];
        if (m_pFeatureReportBuffer)
            ZeroMemory (m_pFeatureReportBuffer, m_HidCaps.FeatureReportByteLength);
    }

    if (m_HidCaps.NumberFeatureButtonCaps)
    {
        m_pFeatureButtonCaps = new HIDP_BUTTON_CAPS[m_HidCaps.NumberFeatureButtonCaps];
        if (!m_pFeatureButtonCaps)
            return false;

        ZeroMemory (m_pFeatureButtonCaps, m_HidCaps.NumberFeatureButtonCaps * sizeof(HIDP_BUTTON_CAPS));

        numCaps = m_HidCaps.NumberFeatureButtonCaps;
        ::HidP_GetButtonCaps (HidP_Feature,
                            m_pFeatureButtonCaps,
                            &numCaps,
                            m_pHidPpd);
    }

    if (m_HidCaps.NumberFeatureValueCaps)
    {
        m_pFeatureValueCaps = new HIDP_VALUE_CAPS[m_HidCaps.NumberFeatureValueCaps];
        if (!m_pFeatureValueCaps)
            return false;

        ZeroMemory (m_pFeatureValueCaps, m_HidCaps.NumberFeatureValueCaps * sizeof(HIDP_VALUE_CAPS));

        numCaps = m_HidCaps.NumberFeatureValueCaps;
        ::HidP_GetValueCaps (HidP_Feature,
                            m_pFeatureValueCaps,
                            &numCaps,
                            m_pHidPpd);
    }

    valueCaps = m_pFeatureValueCaps;
    numValues = 0;

    if (valueCaps)
    {
        for (i = 0; i < m_HidCaps.NumberFeatureValueCaps; i++, valueCaps++) 
        {
            if (valueCaps->IsRange) 
            {
                numValues += valueCaps->Range.UsageMax
                        - valueCaps->Range.UsageMin + 1;
            }
            else
            {
                numValues++;
            }
        }
    }

    m_ulFeatureDataLength = m_HidCaps.NumberFeatureButtonCaps + numValues;

    if (m_ulFeatureDataLength)
    {
        m_pFeatureData = new HID_DATA[m_ulFeatureDataLength];
        if (!m_pFeatureData)
            return false;

        ZeroMemory (m_pFeatureData, m_ulFeatureDataLength * sizeof(HID_DATA));
    }

    buttonCaps = m_pFeatureButtonCaps;
    data = m_pFeatureData;

    if (buttonCaps && data)
    {
        for (i = 0;
            i < m_HidCaps.NumberFeatureButtonCaps;
            i++, data++, buttonCaps++) 
        {
            data->IsButtonData = true;
            data->Status = HIDP_STATUS_SUCCESS;
            data->UsagePage = buttonCaps->UsagePage;

            if (buttonCaps->IsRange)
            {
                data->ButtonData.UsageMin = buttonCaps->Range.UsageMin;
                data->ButtonData.UsageMax = buttonCaps->Range.UsageMax;
            }
            else
            {
                data->ButtonData.UsageMin = data->ButtonData.UsageMax = buttonCaps->NotRange.Usage;
            }
            
            data->ButtonData.MaxUsageLength = ::HidP_MaxUsageListLength (
                                                    HidP_Feature,
                                                    buttonCaps->UsagePage,
                                                    m_pHidPpd);

            data->ButtonData.Usages = new USAGE[data->ButtonData.MaxUsageLength];
            ZeroMemory (data->ButtonData.Usages, data->ButtonData.MaxUsageLength * sizeof(USAGE));

            data->ReportID = buttonCaps->ReportID;
        }
    }

    valueCaps = m_pFeatureValueCaps;

    if (valueCaps)
    {
        for (i = 0; i < numValues; i++, valueCaps++) 
        {
            if (valueCaps->IsRange)
            {
                for (usage = valueCaps->Range.UsageMin;
                    usage <= valueCaps->Range.UsageMax;
                    usage++)
                {
                    data->IsButtonData = false;
                    data->Status = HIDP_STATUS_SUCCESS;
                    data->UsagePage = valueCaps->UsagePage;
                    data->ValueData.Usage = usage;
                    data->ReportID = valueCaps->ReportID;
                    data++;
                }
            } 
            else
            {
                data->IsButtonData = false;
                data->Status = HIDP_STATUS_SUCCESS;
                data->UsagePage = valueCaps->UsagePage;
                data->ValueData.Usage = valueCaps->NotRange.Usage;
                data->ReportID = valueCaps->ReportID;
                data++;
            }
        }
    }

    return true;
}

// closes HID device handle, unregisters HID device notification, 
// frees prepased data and data/report buffers
bool CHidDevice::CloseHidDevice()
{
    // close handle, if valid
    if (m_hDevice != INVALID_HANDLE_VALUE)
    {
        ::CloseHandle(m_hDevice);
        m_hDevice = INVALID_HANDLE_VALUE;
    }

    // if HID device is registered, then unregister
    if (m_hDeviceRegistered) 
    {
        ::UnregisterDeviceNotification(        PostMessage(m_hWnd, WM_UNREGISTER_HANDLE, 0, (LPARAM) m_hDeviceRegistered);
        m_hDeviceRegistered = NULL;
        m_hWnd = NULL;
    }
    
    // free preparsed data
    if (m_pHidPpd)
    {
        ::HidD_FreePreparsedData(m_pHidPpd);
        m_pHidPpd = NULL;
    }

    // free input report buffer
    if (m_pInputReportBuffer)
    {
        delete [] m_pInputReportBuffer;
        m_pInputReportBuffer = NULL;
    }

    // free input data buffer
    if (m_pInputData)
    {
        PHID_DATA data = m_pInputData;

        if (data->ButtonData.Usages)
        {
            delete [] data->ButtonData.Usages;
        }

        delete [] m_pInputData;
        m_pInputData = NULL;
    }

    // free input button caps
    if (m_pInputButtonCaps)
    {
        delete [] m_pInputButtonCaps;
        m_pInputButtonCaps = NULL;
    }

    // free input value caps
    if (m_pInputValueCaps)
    {
        delete [] m_pInputValueCaps;
        m_pInputValueCaps = NULL;
    }

    // free output report buffer
    if (m_pOutputReportBuffer)
    {
        delete [] m_pOutputReportBuffer;
        m_pOutputReportBuffer = NULL;
    }

    // free output data buffer
    if (m_pOutputData)
    {
        PHID_DATA data = m_pOutputData;

        if (data->ButtonData.Usages)
        {
            delete [] data->ButtonData.Usages;
        }

        delete [] m_pOutputData;
        m_pOutputData = NULL;
    }

    // free output button caps
    if (m_pOutputButtonCaps) 
    {
        delete [] m_pOutputButtonCaps;
        m_pOutputButtonCaps = NULL;
    }

    // free output value caps
    if (m_pOutputValueCaps)
    {
        delete [] m_pOutputValueCaps;
        m_pOutputValueCaps = NULL;
    }

    // free feature report buffer
    if (m_pFeatureReportBuffer)
    {
        delete [] m_pFeatureReportBuffer;
        m_pFeatureReportBuffer = NULL;
    }

    // free feature data buffer
    if (m_pFeatureData) 
    {
        PHID_DATA data = m_pFeatureData;

        if (data->ButtonData.Usages)
        {
            delete [] data->ButtonData.Usages;
        }

        delete [] m_pFeatureData;
        m_pFeatureData = NULL;
    }

    // free feature button caps
    if (m_pFeatureButtonCaps) 
    {
        delete [] m_pFeatureButtonCaps;
        m_pFeatureButtonCaps = NULL;
    }

    // free feature values caps
    if (m_pFeatureValueCaps) 
    {
        delete [] m_pFeatureValueCaps;
        m_pFeatureValueCaps = NULL;
    }

    // set open to false
    m_bOpened = false;

    return true;
}

// reads an input report from the HID device, performs 
// a validity check, and unpacks the report data
bool CHidDevice::Read()
{
    DWORD    bytesRead;

    // read the input report data
    if (!ReadFile (m_hDevice,
                  m_pInputReportBuffer,
                  m_HidCaps.InputReportByteLength,
                  &bytesRead,
                  NULL)) // no overlapped
    {
        return false;
    }

    // make sure it was valid, ensure bytes read is what
    // is expected for this report
    ASSERT (bytesRead == m_HidCaps.InputReportByteLength);
    if (bytesRead != m_HidCaps.InputReportByteLength)
    {
        return false;
    }

    // unpack the report data into a more useful form
    return UnpackReport (HidP_Input,
                         m_pInputReportBuffer,
                         m_HidCaps.InputReportByteLength,
                         m_pInputData,
                         m_ulInputDataLength,
                         m_pHidPpd);
}

// for every report ID, pack a report buffer and write the report data to the HID device
bool CHidDevice::Write()
{
    DWORD     bytesWritten;
    PHID_DATA pData;
    ULONG     Index;
    bool      Status;
    bool      WriteStatus;

    // Begin by looping through the HID_DEVICE's HID_DATA structure and setting
    //   the IsDataSet field to false to indicate that each structure has
    //   not yet been set for this Write call.

    pData = m_pOutputData;
    if (!pData)
        return false;

    for (Index = 0; Index < m_ulOutputDataLength; Index++, pData++) 
    {
        pData->IsDataSet = false;
    }

    // In setting all the data in the reports, we need to pack a report buffer
    //   and call WriteFile for each report ID that is represented by the 
    //   device structure.  To do so, the IsDataSet field will be used to 
    //   determine if a given report field has already been set.

    Status = true;

    pData = m_pOutputData;
    if (!pData)
        return false;

    for (Index = 0; Index < m_ulOutputDataLength; Index++, pData++) 
    {
        if (!pData->IsDataSet) 
        {
            // Package the report for this data structure.  PackReport will
            //    set the IsDataSet fields of this structure and any other 
            //    structures that it includes in the report with this structure

            PackReport (HidP_Output,
                     m_pOutputReportBuffer,
                     m_HidCaps.OutputReportByteLength,
                     pData,
                     m_ulOutputDataLength - Index,
                     m_pHidPpd);

            // Now a report has been packaged up...Send it down to the device

            WriteStatus = WriteFile (m_hDevice,
                                  m_pOutputReportBuffer,
                                  m_HidCaps.OutputReportByteLength,
                                  &bytesWritten,
                                  NULL) && (bytesWritten == m_HidCaps.OutputReportByteLength);

            Status = Status && WriteStatus;                         
        }
    }

    return Status;
}

// obtains the feature report from each report ID exposed by the HID device
bool CHidDevice::GetFeature()
{
    ULONG     Index;
    PHID_DATA pData;
    BOOLEAN   bReportStatus;
    bool      bStatus;

    // Begin by looping through the HID_DEVICE's HID_DATA structure and setting
    //   the IsDataSet field to false to indicate that each structure has
    //   not yet been set for this SetFeature() call.

    pData = m_pFeatureData;
    if (!pData)
        return false;

    for (Index = 0; Index < m_ulFeatureDataLength; Index++, pData++) 
    {
        pData->IsDataSet = false;
    }

    // Next, each structure in the HID_DATA buffer is filled in with a value
    //   that is retrieved from one or more calls to HidD_GetFeature.  The 
    //   number of calls is equal to the number of reportIDs on the device

    bStatus = true;

    pData = m_pFeatureData;
    if (!pData)
        return false;

    for (Index = 0; Index < m_ulFeatureDataLength; Index++, pData++) 
    {
        // If a value has yet to have been set for this structure, build a report
        //    buffer with its report ID as the first byte of the buffer and pass
        //    it in the HidD_GetFeature call.  Specifying the report ID in the
        //    first specifies which report is actually retrieved from the device.
        //    The rest of the buffer should be zeroed before the call

        if (!pData->IsDataSet) 
        {
            memset(m_pFeatureReportBuffer, 0x00, m_HidCaps.FeatureReportByteLength);

            m_pFeatureReportBuffer[0] = (UCHAR) pData->ReportID;

            bReportStatus = HidD_GetFeature (m_hDevice,
                                            m_pFeatureReportBuffer,
                                            m_HidCaps.FeatureReportByteLength);

            // If the return value is true, scan through the rest of the HID_DATA
            //    structures and fill whatever values we can from this report

            if (bReportStatus) 
            {
                bReportStatus = UnpackReport (HidP_Feature,
                                              m_pFeatureReportBuffer,
                                              m_HidCaps.FeatureReportByteLength,
                                              m_pFeatureData,
                                              m_ulFeatureDataLength,
                                              m_pHidPpd);
            }

            bStatus = bStatus && bReportStatus;
        }
   }

   return bStatus;
}

// send a feature report for each report ID exposed by the HID device
bool CHidDevice::SetFeature()
{
    PHID_DATA pData;
    ULONG     Index;
    BOOLEAN   bReportStatus;
    bool      bStatus;

    // Begin by looping through the HID_DEVICE's HID_DATA structure and setting
    //   the IsDataSet field to false to indicate that each structure has
    //   not yet been set for this SetFeature() call.

    pData = m_pFeatureData;
    if (!pData)
        return false;

    for (Index = 0; Index < m_ulFeatureDataLength; Index++, pData++) 
    {
        pData->IsDataSet = false;
    }

    // In setting all the data in the reports, we need to pack a report buffer
    //   and call WriteFile for each report ID that is represented by the 
    //   device structure.  To do so, the IsDataSet field will be used to 
    //   determine if a given report field has already been set.

    bStatus = true;

    pData = m_pFeatureData;
    if (!pData)
        return false;

    for (Index = 0; Index < m_ulFeatureDataLength; Index++, pData++) 
    {
        if (!pData->IsDataSet) 
        {
            // Package the report for this data structure.  PackReport will
            //    set the IsDataSet fields of this structure and any other 
            //    structures that it includes in the report with this structure

            PackReport (HidP_Feature,
                        m_pFeatureReportBuffer,
                        m_HidCaps.FeatureReportByteLength,
                        m_pFeatureData,
                        m_ulFeatureDataLength - Index,
                        m_pHidPpd);

            bReportStatus =(HidD_SetFeature (m_hDevice,
                                                m_pFeatureReportBuffer,
                                                m_HidCaps.FeatureReportByteLength));

            bStatus = bStatus && bReportStatus;
        }
    }

    return bStatus;
}

// scans though the HID data report and fills in any values it can
bool CHidDevice::UnpackReport(
                              HIDP_REPORT_TYPE ReportType,
                              PCHAR ReportBuffer,
                              USHORT ReportBufferLength,
                              PHID_DATA Data,
                              ULONG DataLength,
                              PHIDP_PREPARSED_DATA Ppd
                             )
{
    ULONG       numUsages; // Number of usages returned from GetUsages.
    ULONG       i;
    UCHAR       reportID;
    ULONG       Index;
    ULONG       nextUsage;

    reportID = ReportBuffer[0];

    for (i = 0; i < DataLength; i++, Data++) 
    {
        if (reportID == Data->ReportID) 
        {
            if (Data->IsButtonData) 
            {
                numUsages = Data->ButtonData.MaxUsageLength;

                Data->Status = HidP_GetUsages (ReportType,
                                               Data->UsagePage,
                                               0, // All collections
                                               Data->ButtonData.Usages,
                                               &numUsages,
                                               Ppd,
                                               ReportBuffer,
                                               ReportBufferLength);


                // Get usages writes the list of usages into the buffer
                // Data->ButtonData.Usages newUsage is set to the number of usages
                // written into this array.
                // A usage cannot not be defined as zero, so we'll mark a zero
                // following the list of usages to indicate the end of the list of
                // usages
                //
                // NOTE: One anomaly of the GetUsages function is the lack of ability
                //        to distinguish the data for one ButtonCaps from another
                //        if two different caps structures have the same UsagePage
                //        For instance:
                //          Caps1 has UsagePage 07 and UsageRange of 0x00 - 0x167
                //          Caps2 has UsagePage 07 and UsageRange of 0xe0 - 0xe7
                //
                //        However, calling GetUsages for each of the data structs
                //          will return the same list of usages.  It is the 
                //          responsibility of the caller to set in the HID_DEVICE
                //          structure which usages actually are valid for the
                //          that structure. 
                //      

                // Search through the usage list and remove those that 
                //    correspond to usages outside the define ranged for this
                //    data structure.
                
                for (Index = 0, nextUsage = 0; Index < numUsages; Index++) 
                {
                    if (Data->ButtonData.UsageMin <= Data->ButtonData.Usages[Index] &&
                            Data->ButtonData.Usages[Index] <= Data->ButtonData.UsageMax) 
                    {
                        Data->ButtonData.Usages[nextUsage++] = Data->ButtonData.Usages[Index];
                    }
                }

                if (nextUsage < Data->ButtonData.MaxUsageLength) 
                {
                    Data->ButtonData.Usages[nextUsage] = 0;
                }
            }
            else 
            {
                Data->Status = HidP_GetUsageValue (
                                                ReportType,
                                                Data->UsagePage,
                                                0,               // All Collections.
                                                Data->ValueData.Usage,
                                                &Data->ValueData.Value,
                                                Ppd,
                                                ReportBuffer,
                                                ReportBufferLength);

                if (HIDP_STATUS_SUCCESS != Data->Status)
                {
                    return false;
                }

                Data->Status = HidP_GetScaledUsageValue (
                                                       ReportType,
                                                       Data->UsagePage,
                                                       0, // All Collections.
                                                       Data->ValueData.Usage,
                                                       &Data->ValueData.ScaledValue,
                                                       Ppd,
                                                       ReportBuffer,
                                                       ReportBufferLength);
            } 

            Data->IsDataSet = true;
        }
    }

    return true;
}

// packages the HID report based on the data in the structures
bool CHidDevice::PackReport(
                            HIDP_REPORT_TYPE ReportType,
                            PCHAR ReportBuffer,
                            USHORT ReportBufferLength,
                            PHID_DATA Data,
                            ULONG DataLength,
                            PHIDP_PREPARSED_DATA Ppd
                           )
{
    ULONG       numUsages; // Number of usages to set for a given report.
    ULONG       i;
    ULONG       CurrReportID;

    // All report buffers that are initially sent need to be zero'd out

    memset (ReportBuffer, (UCHAR) 0, ReportBufferLength);

    // Go through the data structures and set all the values that correspond to
    //   the CurrReportID which is obtained from the first data structure 
    //   in the list

    CurrReportID = Data->ReportID;

    for (i = 0; i < DataLength; i++, Data++) 
    {
        // There are two different ways to determine if we set the current data
        //    structure: 
        //    1) Store the report ID were using and only attempt to set those
        //        data structures that correspond to the given report ID.  This
        //        example shows this implementation.
        //
        //    2) Attempt to set all of the data structures and look for the 
        //        returned status value of HIDP_STATUS_INVALID_REPORT_ID.  This 
        //        error code indicates that the given usage exists but has a 
        //        different report ID than the report ID in the current report 
        //        buffer

        if (Data->ReportID == CurrReportID) 
        {
            if (Data->IsButtonData) 
            {
                numUsages = Data->ButtonData.MaxUsageLength;
                Data->Status = HidP_SetUsages (ReportType,
                                               Data->UsagePage,
                                               0, // All collections
                                               Data->ButtonData.Usages,
                                               &numUsages,
                                               Ppd,
                                               ReportBuffer,
                                               ReportBufferLength);
            }
            else
            {
                Data->Status = HidP_SetUsageValue (ReportType,
                                                   Data->UsagePage,
                                                   0, // All Collections.
                                                   Data->ValueData.Usage,
                                                   Data->ValueData.Value,
                                                   Ppd,
                                                   ReportBuffer,
                                                   ReportBufferLength);
            }

            if (HIDP_STATUS_SUCCESS != Data->Status)
            {
                return false;
            }

            Data->IsDataSet = true;
        }
    }   

    return true;
}

// obtains the manufacturer string from the HID device
bool CHidDevice::GetManufacturerString(PCHAR szManufacturerString, ULONG ulManufacturerStringLength)
{
    PWCHAR str = new WCHAR[ulManufacturerStringLength];

    if (str)
    {
        // attempt to get the manufacturer string, which is in Unicode format
        BOOLEAN result = HidD_GetManufacturerString(m_hDevice, str, ulManufacturerStringLength*sizeof(WCHAR));

        if (result)
        {
            // converts the wide character string (Unicode) to multi-byte string
            SIZE_T nBytes = wcstombs(szManufacturerString, str, ulManufacturerStringLength-1); 
            if ((SIZE_T) -1 != nBytes) 
            {
                delete str;
                return true;
            }
        }

        delete str;
    }

    return false;
}

// obtains the product string from the HID device
bool CHidDevice::GetProductString(PCHAR szProductString, ULONG ulProductStringLength)
{
    PWCHAR str = new WCHAR[ulProductStringLength];

    if (str)
    {
        // attempt to get the product string, which is in Unicode format
        BOOLEAN result = HidD_GetProductString(m_hDevice, str, ulProductStringLength*sizeof(WCHAR));

        if (result)
        {
            // converts the wide character string (Unicode) to multi-byte string
            SIZE_T nBytes = wcstombs(szProductString, str, ulProductStringLength-1); 
            if ((SIZE_T) -1 != nBytes) 
            {
                delete str;
                return true;
            }
        }

        delete str;
    }

    return false;
}

// obtains the serial numbert string from the HID device
bool CHidDevice::GetSerialNumberString(PCHAR szSerialNumberString, ULONG ulSerialNumberStringLength)
{
    PWCHAR str = new WCHAR[ulSerialNumberStringLength];

    if (str)
    {
        // attempt to get the serial number string, which is in Unicode format
        BOOLEAN result = HidD_GetSerialNumberString(m_hDevice, str, ulSerialNumberStringLength*sizeof(WCHAR));

        if (result)
        {
            // converts the wide character string (Unicode) to multi-byte string
            SIZE_T nBytes = wcstombs(szSerialNumberString, str, ulSerialNumberStringLength-1); 
            if ((SIZE_T) -1 != nBytes) 
            {
                delete str;
                return true;
            }
        }

        delete str;
    }

    return false;
}

// attempts to get a registry value from the registry key where the 
// device-specific configuration information is stored for the HID device
bool CHidDevice::RegQueryValue(LPCTSTR lpValueName,
                                LPDWORD lpType,
                                LPBYTE lpData,
                                LPDWORD lpcbData)
{
    bool ret = false;

    HDEVINFO hwDeviceInfo;
    SP_DEVICE_INTERFACE_DATA devInterfaceData;
    GUID hidGuid;


    // get a copy of the HID GUID to be used below
    HidD_GetHidGuid (&hidGuid);

    // get a list of all HID devices present in the system
    hwDeviceInfo = SetupDiGetClassDevs(&hidGuid, NULL, NULL, DIGCF_PRESENT|DIGCF_INTERFACEDEVICE);
    if (hwDeviceInfo != INVALID_HANDLE_VALUE)        
    {
        HKEY hkEnum;            

        // loop through all HID devices, looking for a match for this specific HID device
        int iEnumInfo = 0;      
        devInterfaceData.cbSize = sizeof(devInterfaceData);
        while ( SetupDiEnumDeviceInterfaces(hwDeviceInfo, 0, (CONST LPGUID)&hidGuid, iEnumInfo++, &devInterfaceData) )
        {
            SP_DEVINFO_DATA devInfoData;
            PSP_INTERFACE_DEVICE_DETAIL_DATA functionClassDeviceData;
            ULONG requiredLength = 0;
            ULONG predictedLength = 0;

            SetupDiGetInterfaceDeviceDetail (
                    hwDeviceInfo,
                    &devInterfaceData,
                    NULL, 
                    0, 
                    &requiredLength,
                    NULL); 

            predictedLength = requiredLength;

            // allocate the device data
            devInfoData.cbSize = sizeof(devInfoData);
            functionClassDeviceData = (PSP_DEVICE_INTERFACE_DETAIL_DATA) new UCHAR[predictedLength];
            functionClassDeviceData->cbSize = sizeof (SP_INTERFACE_DEVICE_DETAIL_DATA);

            // retrieve the information from Plug and Play
            if (SetupDiGetInterfaceDeviceDetail(hwDeviceInfo, &devInterfaceData, functionClassDeviceData, predictedLength, &requiredLength, &devInfoData)) 
            {
                // check to see if this HID device is a match
                if (strcmpi(functionClassDeviceData->DevicePath, m_pHidDevicePath) == 0)
                {
                    // found a match, open a handle to the registry
                    // where device-specific configuration information 
                    // is stored
                    hkEnum = SetupDiOpenDevRegKey ( hwDeviceInfo,
                        &devInfoData,
                        DICS_FLAG_GLOBAL,
                        0,
                        DIREG_DEV,
                        KEY_ALL_ACCESS );

                    if (hkEnum != INVALID_HANDLE_VALUE)
                    {
                        // if the reg handle was open, attempt to obtain
                        // the registry value
                        if (RegQueryValueEx(hkEnum, 
                            lpValueName, 
                            NULL, 
                            lpType, 
                            lpData, 
                            lpcbData
                            ) == ERROR_SUCCESS)
                        {
                            // indicate success
                            ret = true;
                        }

                        // close the reg handle
                        RegCloseKey ( hkEnum );
                    }
    
                    // release the allocated device data and 
                    // leave the loop
                    delete [] functionClassDeviceData;
                    break;
                }
            }

            // release the allocated device data
            delete [] functionClassDeviceData;
        }

        // destroy the list that was created above
        SetupDiDestroyDeviceInfoList(hwDeviceInfo);
    }

    return ret;
}

// attempts to set a registry value in the registry key where the 
// device-specific configuration information is stored for the HID device
bool CHidDevice::RegSetValue(LPCTSTR lpValueName,
                                DWORD Type,
                                LPBYTE lpData,
                                DWORD cbData)
{
    bool ret = false;

    HDEVINFO hwDeviceInfo;
    SP_DEVICE_INTERFACE_DATA devInterfaceData;
    GUID hidGuid;


    // get a copy of the HID GUID to be used below
    HidD_GetHidGuid (&hidGuid);

    // get a list of all HID devices present in the system
    hwDeviceInfo = SetupDiGetClassDevs(&hidGuid, NULL, NULL, DIGCF_PRESENT|DIGCF_INTERFACEDEVICE);
    if (hwDeviceInfo != INVALID_HANDLE_VALUE)        
    {
        HKEY hkEnum;            

        // loop through all HID devices, looking for a match for this specific HID device
        int iEnumInfo = 0;      
        devInterfaceData.cbSize = sizeof(devInterfaceData);
        while ( SetupDiEnumDeviceInterfaces(hwDeviceInfo, 0, (CONST LPGUID)&hidGuid, iEnumInfo++, &devInterfaceData) )
        {
            SP_DEVINFO_DATA devInfoData;
            PSP_INTERFACE_DEVICE_DETAIL_DATA functionClassDeviceData;
            ULONG requiredLength = 0;
            ULONG predictedLength = 0;

            SetupDiGetInterfaceDeviceDetail (
                    hwDeviceInfo,
                    &devInterfaceData,
                    NULL, 
                    0, 
                    &requiredLength,
                    NULL); 

            predictedLength = requiredLength;

            // allocate the device data
            devInfoData.cbSize = sizeof(devInfoData);
            functionClassDeviceData = (PSP_DEVICE_INTERFACE_DETAIL_DATA) new UCHAR[predictedLength];
            functionClassDeviceData->cbSize = sizeof (SP_INTERFACE_DEVICE_DETAIL_DATA);

            // retrieve the information from Plug and Play
            if (SetupDiGetInterfaceDeviceDetail(hwDeviceInfo, &devInterfaceData, functionClassDeviceData, predictedLength, &requiredLength, &devInfoData)) 
            {
                // check to see if this HID device is a match
                if (strcmpi(functionClassDeviceData->DevicePath, m_pHidDevicePath) == 0)
                {
                    // found a match, open a handle to the registry
                    // where device-specific configuration information 
                    // is stored
                    hkEnum = SetupDiOpenDevRegKey ( hwDeviceInfo,
                        &devInfoData,
                        DICS_FLAG_GLOBAL,
                        0,
                        DIREG_DEV,
                        KEY_ALL_ACCESS );

                    if (hkEnum != INVALID_HANDLE_VALUE)
                    {
                        if (RegSetValueEx(hkEnum, 
                            lpValueName, 
                            NULL, 
                            Type, 
                            lpData, 
                            cbData
                            ) == ERROR_SUCCESS)
                        {
                            // indicate success
                            ret = true;
                        }

                        // close the reg handle
                        RegCloseKey ( hkEnum );
                    }

                    // release the allocated device data and 
                    // leave the loop
                    delete [] functionClassDeviceData;
                    break;
                }
            }

            // release the allocated device data
            delete [] functionClassDeviceData;
        }

        // destroy the list that was created above
        SetupDiDestroyDeviceInfoList(hwDeviceInfo);
    }

    return ret;
}


CHidManager::CHidManager()
{
    m_OS.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
    GetVersionEx( &m_OS );

    InitializeHidManager();
}

CHidManager::~CHidManager(void)
{
    // remove all HID devices in the maintained list
    RemoveAllHidDevices();

    // destroy the invisible window, if it exists
    if (m_hWnd)
    {
        ::DestroyWindow(m_hWnd);
        m_hWnd = NULL;
    }

    // if we registered for notificiation for this device, then clean up
    if (m_hDeviceNotification)
    {
        // issue callback indicating the device notification is being unregistered
        if (m_pHidCallback) (*m_pHidCallback)(m_pHidCallbackParam, NULL, HIDCB_NOTIFY_DEVICE_UNREGISTERED);

        // unregister device notification
        // NOTE: for Windows 98x, it can become unstable if you unregister the device notification
        if (m_OS.dwMajorVersion > 4)
        {
        ::UnregisterDeviceNotification(m_hDeviceNotification);
        }
        m_hDeviceNotification = NULL;
    }

    // NULL callback function and parameter pointers
    m_pHidCallback = NULL;
    m_pHidCallbackParam = NULL;
}

// standard initialization
void CHidManager::InitializeHidManager()
{
    m_pThis = this;

    m_bEnabled = false;
    m_hWnd = NULL;
    m_hDeviceNotification = NULL;
    m_pHidCallback = NULL;
    m_pHidCallbackParam = NULL;

    m_pHidDeviceListHead = NULL;
    m_pCurrentHidDevice = NULL;
    m_nHidDevices = 0;

    ZeroMemory(m_bUniqueIDs, HIDMGR_SUPPORTED_HID_DEVICES);
}

// registers for notification of events for all HID class devices and
// calls HID callback function to indicate registration was completed
bool CHidManager::RegisterHidNotification(HWND hWnd)
{
    DEV_BROADCAST_DEVICEINTERFACE broadcastInterface = {0};

    broadcastInterface.dbcc_size = sizeof(broadcastInterface);
    broadcastInterface.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;

    // get HID GUID
    ::HidD_GetHidGuid(&broadcastInterface.dbcc_classguid);

    m_hDeviceNotification = ::RegisterDeviceNotification(hWnd, (PVOID) &broadcastInterface, DEVICE_NOTIFY_WINDOW_HANDLE);
    if (!m_hDeviceNotification) return false;

    if (m_pHidCallback) (*m_pHidCallback)(m_pHidCallbackParam, NULL, HIDCB_NOTIFY_DEVICE_REGISTERED);

    return true;
}

// registers a window class for subsequent use in calls to CreateWindow, used below
ATOM CHidManager::RegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEX wcex;

    wcex.cbSize = sizeof(WNDCLASSEX); 

    wcex.style          = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
    wcex.lpfnWndProc    = (WNDPROC)WindowProc;
    wcex.cbClsExtra     = 0;
    wcex.cbWndExtra     = 0;
    wcex.hInstance      = hInstance;
    wcex.hIcon          = 0;
    wcex.hCursor        = 0;
    wcex.hbrBackground  = 0;
    wcex.lpszMenuName   = 0;
    wcex.lpszClassName  = HIDMANAGER_CLASS;
    wcex.hIconSm        = 0;

    return ::RegisterClassEx(&wcex);
}

// creates an invisible window and uses the returned window handle 
// to register for HID device notification events, then it creates 
// a list of existing HID devices that will be maintained by the 
// HID manager
bool CHidManager::Create(HINSTANCE hInst, HWND hParentWnd, PHidCallback pHidCallback, void *pHidCallbackParam)
{
    bool bResult = true;

    m_pHidCallback = pHidCallback;
    m_pHidCallbackParam = pHidCallbackParam;

    // class so WindowProc function is called
    RegisterClass(hInst);

    // Create an invisible window
    m_hWnd = ::CreateWindow(HIDMANAGER_CLASS, _T(""), WS_POPUP, 
                            CW_USEDEFAULT,CW_USEDEFAULT, 
                            CW_USEDEFAULT,CW_USEDEFAULT, 
                            NULL, 0,
                            hInst, 0);

    if (!RegisterHidNotification(m_hWnd))
    {
        bResult = false;
    }

    if (bResult)
    {
        // setup all existing and valid HID devices
        RefreshHidDevices();

        // everything was created successfully 
        m_bEnabled = true;
    }

    return bResult;
}

// attempts to open a handle to the HID device to determine 
// if it is present (or not) and returns the result
bool CHidManager::IsHidDevicePresent(CHidDevice *pHidDevice)
{
    // attempt to open a handle to the HID device, if the device
    // is present then this should return success
    HANDLE h = ::CreateFile(pHidDevice->m_pHidDevicePath,
                            GENERIC_WRITE | GENERIC_READ,
                            FILE_SHARE_WRITE | FILE_SHARE_READ,
                            NULL,
                            OPEN_EXISTING,
                            0,
                            NULL);

    if (h != INVALID_HANDLE_VALUE)
    {
        // close handle and indicate device is present
        ::CloseHandle(h);
        return true;
    }

    return false;
}

// attempts to create and maintain a unique ID for the associated HID device
USHORT CHidManager::CreateUniqueDeviceID()
{
    USHORT i;

    for (i=0;i<HIDMGR_SUPPORTED_HID_DEVICES;i++)
    {
        if (!m_bUniqueIDs[i])
        {
            m_bUniqueIDs[i] = true;
            return i;
        }
    }

    return 0xffff;
}

// frees the specified unique ID
void CHidManager::FreeUniqueDeviceID(USHORT uID)
{
    if (uID < HIDMGR_SUPPORTED_HID_DEVICES)
    {
        m_bUniqueIDs[uID] = false;
    }
}

// returns a pointer to the first HID device in the list
CHidDevice* CHidManager::GetFirstHidDevice()
{
    m_pCurrentHidDevice = m_pHidDeviceListHead;
    return m_pCurrentHidDevice;
}

// returns a pointer to the next HID device in the list
CHidDevice* CHidManager::GetNextHidDevice()
{
    if (m_pCurrentHidDevice)
        m_pCurrentHidDevice = m_pCurrentHidDevice->nextHidDevice;

    return m_pCurrentHidDevice;
}

// returns a pointer to the current HID device in the list
CHidDevice* CHidManager::GetCurrentHidDevice()
{
    return m_pCurrentHidDevice;
}

// scans the current list of HID devices and returns a pointer 
// to the HID device that matches the device path provided
CHidDevice* CHidManager::GetHidDeviceWithPath(PCHAR pDevicePath)
{
    CHidDevice * pCurHidDevice = m_pHidDeviceListHead;

    while (pCurHidDevice)
    {
        if (!lstrcmpi(pCurHidDevice->m_pHidDevicePath, pDevicePath))
            break;

        pCurHidDevice = pCurHidDevice->nextHidDevice;
    }

    return pCurHidDevice;
}

// scans the current list of HID devices and returns a pointer 
// to the HID device that matches the device handle provided
CHidDevice* CHidManager::GetHidDeviceWithHandle(HANDLE hHidDevice)
{
    CHidDevice * pCurHidDevice = m_pHidDeviceListHead;

    while (pCurHidDevice)
    {
        if (pCurHidDevice->GetDeviceHandle() == hHidDevice)
            break;

        pCurHidDevice = pCurHidDevice->nextHidDevice;
    }

    return pCurHidDevice;
}

// determines if the HID device already exists in the list
bool CHidManager::HidDeviceAlreadyExist(CHidDevice *pHidDevice)
{
    CHidDevice * pCurHidDevice = m_pHidDeviceListHead;

    while (pCurHidDevice)
    {
        if (pCurHidDevice == pHidDevice)
            return true;

        pCurHidDevice = pCurHidDevice->nextHidDevice;
    }

    return false;
}

// checks if the provided HID device already exists, and if not, 
// adds the new HID device to the end of the list, increments the 
// HID device counter, and call the HID callback function to indicate 
// a new HID device was added
bool CHidManager::AddHidDevice(CHidDevice *pHidDevice)
{
    // determine if HID device already exists
    if (HidDeviceAlreadyExist(pHidDevice))
        return false;

    // add HID device to end of list, if first device
    // then set it as the first device in the list
    if (m_pHidDeviceListHead == NULL)
    {
        m_pHidDeviceListHead = pHidDevice;
    }
    else
    {
        CHidDevice * pHidTemp = m_pHidDeviceListHead;

        while (pHidTemp->nextHidDevice != NULL)
        {
            pHidTemp = pHidTemp->nextHidDevice;
        }

        pHidTemp->nextHidDevice = pHidDevice;
    }

    // increment HID device counter
    m_nHidDevices++;

    // call HID callback function to indicate HID device was added
    if (m_pHidCallback) (*m_pHidCallback)(m_pHidCallbackParam, pHidDevice, HIDCB_NOTIFY_DEVICE_ADDED);

    return true;
}

// closes outstanding handle to the HID device, call HID 
// callback function to indicate HID device is being removed, 
// removes the HID device from the list, and deletes the HID device
bool CHidManager::RemoveHidDevice(CHidDevice *pHidDevice)
{
    // check for valid HID device pointer
    if (pHidDevice)
    {
        // close handle to HID device
        pHidDevice->CloseHidDevice();

        CHidDevice * pHidTemp = m_pHidDeviceListHead;
        CHidDevice * pHidPrev = NULL;

        // loop through all HID devices in the list to find the 
        // matching HID device to be removed
        while (pHidTemp)
        {
            // check if HID device was found
            if (pHidTemp == pHidDevice)
            {
                // found, so decrement HID device counter
                m_nHidDevices--;

                // call HID callback function to indicate HID device is 
                // being removed
                if (m_pHidCallback) (*m_pHidCallback)(m_pHidCallbackParam, pHidDevice, HIDCB_NOTIFY_DEVICE_REMOVED);

                // remove HID device from list
                if (pHidPrev)
                {
                    pHidPrev->nextHidDevice = pHidTemp->nextHidDevice;
                }

                if (pHidTemp == m_pHidDeviceListHead)
                {
                    m_pHidDeviceListHead = pHidTemp->nextHidDevice;
                }

                // delete HID device
                DeleteHidDevice(pHidTemp);

                // end loop
                pHidTemp = NULL;
            }
            else
            {
                // get next HID device in list
                pHidPrev = pHidTemp;
                pHidTemp = pHidTemp->nextHidDevice;
            }
        }
    }
    
    return true;
}

// scans though all HID devices in the list and removes them
void CHidManager::RemoveAllHidDevices()
{
    CHidDevice * pCurHidDevice = m_pHidDeviceListHead;

    while (pCurHidDevice)
    {
        CHidDevice * pNextHidDevice = pCurHidDevice->nextHidDevice;

        RemoveHidDevice(pCurHidDevice);

        pCurHidDevice = pNextHidDevice;
    }

    m_pHidDeviceListHead = NULL;
}

// validates that all HID devices in the list are still present, 
// removes those from the list that are currently not present, 
// scans the list of all existing HID devices present, and then 
// attempts to add the existing HID devices to the list
bool CHidManager::RefreshHidDevices(void)
{
    bool                     bRetVal = false;
    CHidDevice               *pCurHidDevice = m_pHidDeviceListHead;

    // first check that all devices are still valid
    // remove if not
    while (pCurHidDevice)
    {
        CHidDevice * pNextHidDevice = pCurHidDevice->nextHidDevice;

        if (!IsHidDevicePresent(pCurHidDevice))
        {
            RemoveHidDevice(pCurHidDevice);
            bRetVal = true;
        }

        pCurHidDevice = pNextHidDevice;
    }


    // open a handle to the plug and play dev node, SetupDiGetClassDevs() 
    // returns a device information set that contains info on all installed 
    // devices of a specified class.
    GUID                             hidGuid;
    HDEVINFO                         hardwareDeviceInfo;
    SP_DEVICE_INTERFACE_DATA         deviceInfoData;
    PSP_DEVICE_INTERFACE_DETAIL_DATA functionClassDeviceData = NULL;
    ULONG                            predictedLength = 0;
    ULONG                            requiredLength = 0;
    ULONG                            i = 0;
    bool                             bDone = false;

    HidD_GetHidGuid (&hidGuid);

    hardwareDeviceInfo = SetupDiGetClassDevs (&hidGuid,
                                                NULL, // define no enumerator (all HID devices)
                                                NULL, // define no
                                                (DIGCF_PRESENT | // only devices present
                                                DIGCF_INTERFACEDEVICE)); // function class devices.

    if (hardwareDeviceInfo == INVALID_HANDLE_VALUE)
    {
        // return true to indicate something may have changed
        return true;
    }

    deviceInfoData.cbSize = sizeof (SP_DEVICE_INTERFACE_DATA);

    while (!bDone)
    {
        if (SetupDiEnumDeviceInterfaces (hardwareDeviceInfo,
                                            0, // don't care about specific PDOs
                                            &hidGuid,
                                            i,
                                            &deviceInfoData))
        {
            //
            // allocate a function class device data structure to receive the
            // goods about this particular device.
            //

            SetupDiGetDeviceInterfaceDetail (
                    hardwareDeviceInfo,
                    &deviceInfoData,
                    NULL, // probing so no output buffer yet
                    0, // probing so output buffer length of zero
                    &requiredLength,
                    NULL); // not interested in the specific dev-node


            predictedLength = requiredLength;

            functionClassDeviceData = (PSP_DEVICE_INTERFACE_DETAIL_DATA) new UCHAR[predictedLength];
            if (functionClassDeviceData)
            {
                ZeroMemory (functionClassDeviceData, predictedLength);
                functionClassDeviceData->cbSize = sizeof (SP_DEVICE_INTERFACE_DETAIL_DATA);
            }
            else
            {
                SetupDiDestroyDeviceInfoList (hardwareDeviceInfo);

                // return true to indicate something may have changed
                return true;
            }

            //
            // retrieve the information from Plug and Play.
            //

            if (! SetupDiGetDeviceInterfaceDetail (
                        hardwareDeviceInfo,
                        &deviceInfoData,
                        functionClassDeviceData,
                        predictedLength,
                        &requiredLength,
                        NULL)) 
            {
                SetupDiDestroyDeviceInfoList (hardwareDeviceInfo);
                delete [] functionClassDeviceData;

                // return true to indicate something may have changed
                return true;
            }

            HidDeviceArrival(functionClassDeviceData->DevicePath); 

            delete [] functionClassDeviceData;
        }
        else 
        {
            if (GetLastError() == ERROR_NO_MORE_ITEMS)
            {
                bDone = true;
            }
        }
        
        i++;
    }

    return bRetVal;
}

// makes sure the HID device does not already exist in the list, 
// and then creates a new HID device device, opens a handle to 
// the device, adds the new HID device to the list, and registers 
// event notification for this new HID device
void CHidManager::HidDeviceArrival(PCHAR pDevicePath)
{
    // attempt to match a HID device in the list
    // based on the device path provided, if not then
    // it is okay to add this HID device to the list
    // otherwise the HID device already exists in our
    // list
    if (!GetHidDeviceWithPath(pDevicePath))
    {
        // this HID device is not in our list, so create 
        // a new one and attempt to add it to the list
        CHidDevice *pNewHidDevice = NewHidDevice(pDevicePath);

        if (pNewHidDevice) 
        {
            // make sure we can open a handle to
            // the HID device before we add it 
            // to the list
            if (pNewHidDevice->OpenHidDevice())
            {
                // handle to the HID device open, so
                // now add it to the list and register
                // it for event notification
                AddHidDevice(pNewHidDevice);
                pNewHidDevice->RegisterHidDevice(m_hWnd);
            }
            else
            {
                // could not open handle, so just delete 
                // the HID device craeated
                DeleteHidDevice(pNewHidDevice);
            }
        }
    }
}

// readies the HID device for removal by making sure the handle is closed, 
// this version expects the device path
void CHidManager::HidDeviceQueryRemoval(PCHAR pDevicePath)
{
    CHidDevice *pHidDevice = GetHidDeviceWithPath(pDevicePath);

    if (pHidDevice)
    {
        pHidDevice->CloseHidDevice();
    }
}

// readies the HID device for removal by making sure the handle is closed, 
// this version expects a handle to the device
void CHidManager::HidDeviceQueryRemoval(HANDLE hHidDevice)
{
    CHidDevice *pHidDevice = GetHidDeviceWithHandle(hHidDevice);

    if (pHidDevice)
    {
        pHidDevice->CloseHidDevice();
    }
}

// removes the HID device, this version expects the 
// device path
void CHidManager::HidDeviceRemoval(PCHAR pDevicePath)
{
    CHidDevice *pHidDevice = GetHidDeviceWithPath(pDevicePath);

    if (pHidDevice)
    {
        RemoveHidDevice(pHidDevice);
    }
}

// removes the HID device, this version expects a
// handle to the device
void CHidManager::HidDeviceRemoval(HANDLE hHidDevice)
{
    CHidDevice *pHidDevice = GetHidDeviceWithHandle(hHidDevice);

    if (pHidDevice)
    {
        RemoveHidDevice(pHidDevice);
    }
}

// the global (static) callback function for the CHidManager window
LRESULT PASCAL CHidManager::WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    CHidManager* pHidManager = m_pThis;

    // make sure our window is the target for this message
    if (pHidManager->GetSafeHwnd() == hWnd)
    { 
        if (message == WM_DEVICECHANGE)
        {
            PDEV_BROADCAST_HDR broadcastHdr = (PDEV_BROADCAST_HDR) lParam;

            if (wParam == DBT_DEVICEARRIVAL)
            {
                if (broadcastHdr->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE)
                {
                    PDEV_BROADCAST_DEVICEINTERFACE pBroadcastInterface = (PDEV_BROADCAST_DEVICEINTERFACE) lParam;

                    // HID device arrival
                    pHidManager->HidDeviceArrival(pBroadcastInterface->dbcc_name);
                }
            }
            else if (wParam == DBT_DEVICEQUERYREMOVE)
            {
                // if this message is received, the device is either
                // being disabled or removed through device manager.
                // To properly handle this request, we need to close
                // the handle to the device.

                if (broadcastHdr->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE)
                {
                    PDEV_BROADCAST_DEVICEINTERFACE pBroadcastInterface = (PDEV_BROADCAST_DEVICEINTERFACE) lParam;

                    // HID device query removal
                    pHidManager->HidDeviceQueryRemoval(pBroadcastInterface->dbcc_name);
                }
                else if (broadcastHdr->dbch_devicetype == DBT_DEVTYP_HANDLE)
                {
                    PDEV_BROADCAST_HANDLE pBroadcastDevice = (PDEV_BROADCAST_HANDLE) lParam;

                    // HID device query removal
                    pHidManager->HidDeviceQueryRemoval(pBroadcastDevice->dbch_handle);
                }
            }
            else if ((wParam == DBT_DEVICEREMOVEPENDING) || (wParam == DBT_DEVICEREMOVECOMPLETE))
            {
                // do the same steps for DBT_DEVICEREMOVEPENDING and 
                // DBT_DEVICEREMOVECOMPLETE  
                
                // the DBT_DEVICEREMOVECOMPLETE request is not called
                // for a device if it is disabled or removed via 
                // the Device Manager, however, in this case it will 
                // receive the DBT_DEVICEREMOVEPENDING  

                // remove the device from our currently displayed
                // list of devices and unregister notification

                if (broadcastHdr->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE)
                {
                    PDEV_BROADCAST_DEVICEINTERFACE pBroadcastInterface = (PDEV_BROADCAST_DEVICEINTERFACE) lParam;

                    // HID device removal
                    pHidManager->HidDeviceRemoval(pBroadcastInterface->dbcc_name);
                }
                else if (broadcastHdr->dbch_devicetype == DBT_DEVTYP_HANDLE)
                {
                    PDEV_BROADCAST_HANDLE pBroadcastDevice = (PDEV_BROADCAST_HANDLE) lParam;

                    // HID device removal
                    pHidManager->HidDeviceRemoval(pBroadcastDevice->dbch_handle);
                }
            }
        }
        else if (message == WM_UNREGISTER_HANDLE)
        {
            ::UnregisterDeviceNotification ( (HDEVNOTIFY) lParam ); 
        }
    }

    // now the process the message, just default it
    return ::DefWindowProc(hWnd, message, wParam, lParam);
}