#pragma warning( disable: 4103)
#include "mmcpl.h"
#include <cpl.h>
#define NOSTATUSBAR
#include <commctrl.h>
#include <prsht.h>
#include <regstr.h>
#include <infstr.h>
#include <devguid.h>

#include "draw.h"
#include "utils.h"
#include "drivers.h"
#include "sulib.h"
#include "medhelp.h"
#include <tchar.h>

#define GetString(_psz,_id) LoadString(myInstance,(_id),(_psz),sizeof((_psz))/sizeof(TCHAR))

// Global info struct. One instance for the whole dialog
typedef struct _OUR_PROP_PARAMS
{
    HDEVINFO            DeviceInfoSet;
    PSP_DEVINFO_DATA    DeviceInfoData;
    HKEY                hkDrv;      // Key to classguid\0000
    HKEY                hkDrivers;  // Key to classguid\0000\Drivers
    BOOL                bClosing;   // Set to TRUE while dialog is closing
    TCHAR szSubClasses[256];         // Subclasses to process
} OUR_PROP_PARAMS, *POUR_PROP_PARAMS;

typedef enum
{
    NodeTypeRoot,
    NodeTypeClass,
    NodeTypeDriver
} NODETYPE;

// Tree node. One per node on tree.
typedef struct _DMTREE_NODE;
typedef BOOL (*PFNCONFIG)     (HWND ParentHwnd, struct _DMTREE_NODE *pTreeNode);
typedef BOOL (*PFNQUERYCONFIG)(HWND ParentHwnd, struct _DMTREE_NODE *pTreeNode);
typedef struct _DMTREE_NODE
{
    NODETYPE NodeType;              // Type of node
    PFNCONFIG pfnConfig;            // Ptr to config function
    PFNQUERYCONFIG pfnQueryConfig;  // Ptr to query config function
    int QueryConfigInfo;            // Data for config function
    TCHAR szDescription[MAXSTR];     // Node description
    TCHAR szDriver[MAXSTR];          // Driver name of this node
    WCHAR wszDriver[MAXSTR];        // Wide char driver name
    TCHAR szAlias[MAXSTR];          // Alias
    WCHAR wszAlias[MAXSTR];         // Wide char alias
    DriverClass dc;                 // Legacy-style driver class, if available
    HTREEITEM hti;                  // For use with MIDI prop sheet callback
} DMTREE_NODE, *PDMTREE_NODE;

INT_PTR APIENTRY DmAdvPropPageDlgProc(IN HWND   hDlg,
                                      IN UINT   uMessage,
                                      IN WPARAM wParam,
                                      IN LPARAM lParam
                                     );

UINT CALLBACK DmAdvPropPageDlgCallback(HWND hwnd,
                                       UINT uMsg,
                                       LPPROPSHEETPAGE ppsp
                                      );

BOOL DmAdvPropPage_OnCommand(
                            HWND ParentHwnd,
                            int  ControlId,
                            HWND ControlHwnd,
                            UINT NotifyCode
                            );

BOOL DmAdvPropPage_OnContextMenu(
                                HWND HwndControl,
                                WORD Xpos,
                                WORD Ypos
                                );

BOOL DmAdvPropPage_OnHelp(
                         HWND       ParentHwnd,
                         LPHELPINFO HelpInfo
                         );

BOOL DmAdvPropPage_OnInitDialog(
                               HWND    ParentHwnd,
                               HWND    FocusHwnd,
                               LPARAM  Lparam
                               );

BOOL DmAdvPropPage_OnNotify(
                           HWND    ParentHwnd,
                           LPNMHDR NmHdr
                           );

void DmAdvPropPage_OnPropertiesClicked(
                                      HWND             ParentHwnd,
                                      POUR_PROP_PARAMS Params
                                      );


BOOL DmOverrideResourcesPage(LPVOID        Info,
                             LPFNADDPROPSHEETPAGE AddFunc,
                             LPARAM               Lparam,
                             POUR_PROP_PARAMS     Params
                            );

BOOL AddCDROMPropertyPage( HDEVINFO             hDeviceInfoSet,
                           PSP_DEVINFO_DATA     pDeviceInfoData,
                           LPFNADDPROPSHEETPAGE AddFunc,
                           LPARAM               Lparam
                          );

BOOL AddSpecialPropertyPage( DWORD                SpecialDriverType,
                             LPFNADDPROPSHEETPAGE AddFunc,
                             LPARAM               Lparam
                            );

BOOL DmInitDeviceTree(HWND hwndTree, POUR_PROP_PARAMS Params);

BOOL DmAdvPropPage_OnDestroy(
                            HWND    ParentHwnd,
                            LPNMHDR NmHdr
                            );

void DoProperties(HWND ParentHwnd, HWND hWndI, HTREEITEM htiCur);

BOOL QueryConfigDriver(HWND ParentHwnd, PDMTREE_NODE pTreeNode)
{
    HANDLE       hDriver;

    if (pTreeNode->NodeType!=NodeTypeDriver)
    {
        return FALSE;
    }

    if (pTreeNode->QueryConfigInfo==0)  // if 0, the we haven't checked yet
    {
        INT_PTR IsConfigurable;

        //  open the driver
        hDriver = OpenDriver(pTreeNode->wszDriver, NULL, 0L);
        if (!hDriver)
        {
            return FALSE;
        }

        // Send the DRV_CONFIGURE message to the driver
        IsConfigurable = SendDriverMessage(hDriver,
                                           DRV_QUERYCONFIGURE,
                                           0L,
                                           0L);

        CloseDriver(hDriver, 0L, 0L);

        // 1->Is configurable, -1->Not configurable
        pTreeNode->QueryConfigInfo = IsConfigurable ? 1 : -1;
    }

    return (pTreeNode->QueryConfigInfo>0);
}

BOOL PNPDriverToIResource(PDMTREE_NODE pTreeNode, IRESOURCE* pir)
{
    IDRIVER tempIDriver;

    if ((pir->pcn = (PCLASSNODE)LocalAlloc (LPTR, sizeof(CLASSNODE))) == NULL)
    {
        return FALSE;
    }

    if (!DriverClassToClassNode(pir->pcn, pTreeNode->dc))
    {
        LocalFree ((HANDLE)pir->pcn);
        return FALSE;
    }

    pir->iNode = 2;   // 1=class, 2=device, 3=acm, 4=instmt

    lstrcpy (pir->szFriendlyName, pTreeNode->szDescription);
    lstrcpy (pir->szDesc,         pTreeNode->szDescription);
    lstrcpy (pir->szFile,         pTreeNode->szDriver);
    lstrcpy (pir->szDrvEntry,     pTreeNode->szAlias);
    lstrcpy (pir->szClass,        pir->pcn->szClass);

    pir->fQueryable = (short)QueryConfigDriver(NULL, pTreeNode);
    pir->iClassID = (short)DriverClassToOldClassID(pTreeNode->dc);
    pir->szParam[0] = 0;
    pir->dnDevNode = 0;
    pir->hDriver = NULL;

    // Find fStatus, which despite its name is really a series of
    // flags--in Win95 it's composed of DEV_* flags (from the old
    // mmcpl.h), but those are tied with PNP.  Here, we use the
    // dwStatus* flags:
    //
    ZeroMemory(&tempIDriver,sizeof(IDRIVER));

    lstrcpy(tempIDriver.wszAlias,pTreeNode->wszAlias);
    lstrcpy(tempIDriver.szAlias,pTreeNode->szAlias);
    lstrcpy(tempIDriver.wszFile,pTreeNode->wszDriver);
    lstrcpy(tempIDriver.szFile,pTreeNode->szDriver);
    lstrcpy(tempIDriver.szDesc,pTreeNode->szDescription);
    lstrcpy(tempIDriver.szSection,wcsstr(pTreeNode->szDescription, TEXT("MCI")) ? szMCI : szDrivers);
    lstrcpy(tempIDriver.wszSection,wcsstr(pTreeNode->szDescription, TEXT("MCI")) ? szMCI : szDrivers);

    pir->fStatus = (int)GetDriverStatus (&tempIDriver);

    return TRUE;
}

BOOL ConfigDriver(HWND ParentHwnd, PDMTREE_NODE pTreeNode)
{
    //need to pop up the legacy properties dialog
    IRESOURCE ir;
    DEVTREENODE dtn;
    TCHAR        szTab[ cchRESOURCE ];

    if ((pTreeNode->NodeType == NodeTypeDriver) && (pTreeNode->dc != dcINVALID))
    {
        if (PNPDriverToIResource(pTreeNode, &ir))
        {
            GetString (szTab, IDS_GENERAL);

            dtn.lParam = (LPARAM)&ir;
            dtn.hwndTree = ParentHwnd;

            //must call this function twice to fill in the array of PIDRIVERs in drivers.c
            //otherwise, many of the "settings" calls won't work
            InitInstalled (GetParent (ParentHwnd), szDrivers);
            InitInstalled (GetParent (ParentHwnd), szMCI);

            switch (pTreeNode->dc)
            {
                case dcMIDI :
                   ShowWithMidiDevPropSheet (szTab,
                                             DevPropDlg,
                                             DLG_DEV_PROP,
                                             ParentHwnd,
                                             pTreeNode->szDescription,
                                             pTreeNode->hti,
                                             (LPARAM)&dtn,
                                             (LPARAM)&ir,
                                             (LPARAM)ParentHwnd);
                break;

                case dcWAVE :
                    ShowPropSheet (szTab,
                              DevPropDlg,
                              DLG_WAVDEV_PROP,
                              ParentHwnd,
                              pTreeNode->szDescription,
                              (LPARAM)&dtn);
                break;

                default:
                   ShowPropSheet (szTab,
                                  DevPropDlg,
                                  DLG_DEV_PROP,
                                  ParentHwnd,
                                  pTreeNode->szDescription,
                                  (LPARAM)&dtn);
                break;
            } //end switch

            FreeIResource (&ir);
        }
    }

    return (FALSE);
}

const static DWORD aDMPropHelpIds[] = {  // Context Help IDs
    IDC_ADV_TREE,    IDH_GENERIC_DEVICES,
    ID_ADV_PROP,     IDH_ADV_PROPERTIES,
    0, 0
};

//******************************************************************************
//* Subtype code
//******************************************************************************
//
// Subtype info. Array of one per device class subtype
typedef struct _SUBTYPE_INFO
{
    TCHAR *szClass;
    DWORD DescId;
    DWORD IconId;
    PFNCONFIG pfnConfig;
    PFNQUERYCONFIG pfnQueryConfig;
    DriverClass dc;
    TCHAR  szDescription[64];
    DWORD IconIndex;
} SUBTYPE_INFO;

static SUBTYPE_INFO SubtypeInfo[] =
{
    { TEXT(""),            IDS_MM_HEADER,       IDI_MMICON,   ConfigDriver, QueryConfigDriver, dcOTHER},
    { TEXT("waveaudio"),   IDS_MCI_HEADER,      IDI_MCI,      ConfigDriver, QueryConfigDriver, dcMCI},
    { TEXT("wavemap"),     IDS_WAVE_HEADER,     IDI_WAVE,     ConfigDriver, QueryConfigDriver, dcWAVE},
    { TEXT("wave"),        IDS_WAVE_HEADER,     IDI_WAVE,     ConfigDriver, QueryConfigDriver, dcWAVE},
    { TEXT("vids"),        IDS_ICM_HEADER,      IDI_ICM,      ConfigDriver, QueryConfigDriver, dcVCODEC},
    { TEXT("vidc"),        IDS_ICM_HEADER,      IDI_ICM,      ConfigDriver, QueryConfigDriver, dcVCODEC},
    { TEXT("sequencer"),   IDS_MCI_HEADER,      IDI_MCI,      ConfigDriver, QueryConfigDriver, dcMCI},
    { TEXT("msvideo"),     IDS_VIDCAP_HEADER,   IDI_VIDEO,    ConfigDriver, QueryConfigDriver, dcVIDCAP},
    { TEXT("msacm"),       IDS_ACM_HEADER,      IDI_ACM,      ConfigDriver, QueryConfigDriver, dcACODEC},
    { TEXT("mpegvideo"),   IDS_MCI_HEADER,      IDI_MCI,      ConfigDriver, QueryConfigDriver, dcMCI},
    { TEXT("mixer"),       IDS_MIXER_HEADER,    IDI_MIXER,    ConfigDriver, QueryConfigDriver, dcMIXER},
    { TEXT("midimapper"),  IDS_MIDI_HEADER,     IDI_MIDI,     ConfigDriver, QueryConfigDriver, dcMIDI},
    { TEXT("midi"),        IDS_MIDI_HEADER,     IDI_MIDI,     ConfigDriver, QueryConfigDriver, dcMIDI},
    { TEXT("mci"),         IDS_MCI_HEADER,      IDI_MCI,      ConfigDriver, QueryConfigDriver, dcMCI},
    { TEXT("icm"),         IDS_ICM_HEADER,      IDI_ICM,      ConfigDriver, QueryConfigDriver, dcVCODEC},
    { TEXT("cdaudio"),     IDS_MCI_HEADER,      IDI_MCI,      ConfigDriver, QueryConfigDriver, dcMCI},
    { TEXT("avivideo"),    IDS_MCI_HEADER,      IDI_MCI,      ConfigDriver, QueryConfigDriver, dcMCI},
    { TEXT("aux"),         IDS_AUX_HEADER,      IDI_AUX,      ConfigDriver, QueryConfigDriver, dcAUX},
    { TEXT("acm"),         IDS_ACM_HEADER,      IDI_ACM,      ConfigDriver, QueryConfigDriver, dcACODEC},
    { TEXT("joy"),         IDS_JOYSTICK_HEADER, IDI_JOYSTICK, ConfigDriver, QueryConfigDriver, dcJOY}
};

#define SUBTYPE_INFO_SIZE (sizeof(SubtypeInfo)/sizeof(SUBTYPE_INFO))

BOOL LoadSubtypeInfo(HWND hwndTree)
{
    UINT i;
    UINT  uFlags;
    int cxMiniIcon;
    int cyMiniIcon;
    DWORD dwLayout;

    HIMAGELIST hImagelist;

    // Create the image list
    cxMiniIcon = (int)GetSystemMetrics(SM_CXSMICON);
    cyMiniIcon = (int)GetSystemMetrics(SM_CYSMICON);
    uFlags = ILC_MASK | ILC_COLOR32;
   
    if (GetProcessDefaultLayout(&dwLayout) &&
            (dwLayout & LAYOUT_RTL)) 
    {
        uFlags |= ILC_MIRROR;
    }

    hImagelist = ImageList_Create(cxMiniIcon, cyMiniIcon, uFlags, SUBTYPE_INFO_SIZE, 4);
    if (!hImagelist)
        return FALSE;

    for (i=0;i<SUBTYPE_INFO_SIZE;i++)
    {
        HICON hIcon;

        // Load the description
        LoadString(ghInstance, SubtypeInfo[i].DescId, SubtypeInfo[i].szDescription, 64);

        // Load the image into the image list
        hIcon = LoadImage (ghInstance,
                           MAKEINTRESOURCE( SubtypeInfo[i].IconId ),
                           IMAGE_ICON,
                           cxMiniIcon,
                           cyMiniIcon,
                           LR_DEFAULTCOLOR);

        if (hIcon)  // PREFIX 160723
        {
            SubtypeInfo[i].IconIndex = ImageList_AddIcon(hImagelist, hIcon);
            DestroyIcon(hIcon);
        }
        else
        {
            SubtypeInfo[i].IconIndex = -1;
        }
    }

    // Clean out and initialize tree control
    TreeView_SetImageList(hwndTree, hImagelist, TVSIL_NORMAL);

    return TRUE;
}

SUBTYPE_INFO *GetSubtypeInfo(TCHAR *pszClass)
{
    UINT iClass;
    if (pszClass)
    {
        for (iClass=0;iClass<SUBTYPE_INFO_SIZE;iClass++)
        {
            if (!lstrcmpi(pszClass,SubtypeInfo[iClass].szClass))
                return &SubtypeInfo[iClass];
        }
    }

    return &SubtypeInfo[0];
}

//******************************************************************************

/*

Routine Description: MediaPropPageProvider

    Entry-point for adding additional device manager property
    sheet pages.  Registry specifies this routine under
    HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\{4D36E96C-E325-11CE-BFC1-08002BE10318}
    EnumPropPage32="mmsys.cpl,thisproc"

    This entry-point gets called only when the DeviceManager asks for additional property pages.

Arguments:

    Info  - points to PROPSHEETPAGE_REQUEST, see setupapi.h
    AddFunc - function ptr to call to add sheet.
    Lparam - add sheet functions private data handle.

Return Value:

    BOOL: FALSE if pages could not be added, TRUE on success

*/
BOOL APIENTRY MediaPropPageProvider(LPVOID               Info,
                                    LPFNADDPROPSHEETPAGE AddFunc,
                                    LPARAM               Lparam
                                   )
{
    PSP_PROPSHEETPAGE_REQUEST pprPropPageRequest;
    PROPSHEETPAGE             psp;
    HPROPSHEETPAGE            hpsp;
    POUR_PROP_PARAMS          Params;
    HDEVINFO         DeviceInfoSet;
    PSP_DEVINFO_DATA DeviceInfoData;
    DWORD SpecialDriverType;

    HKEY hkDrv;
    HKEY hkDrivers;
    DWORD cbLen;

    pprPropPageRequest = (PSP_PROPSHEETPAGE_REQUEST) Info;

    if (pprPropPageRequest->PageRequested != SPPSR_ENUM_ADV_DEVICE_PROPERTIES)
    {
        return TRUE;
    }

    DeviceInfoSet  = pprPropPageRequest->DeviceInfoSet;
    DeviceInfoData = pprPropPageRequest->DeviceInfoData;

    // This API is called for both devices and the class as a whole
    // (when someone right-clicks on the class and chooses properties).
    // In the class case the DeviceInfoData field of the propPageRequest structure is NULL.
    // We don't do anything in that case, so just return.
    if (!DeviceInfoData)
    {
        return TRUE;
    }

    SpecialDriverType = IsSpecialDriver(DeviceInfoSet, DeviceInfoData);
    if (SpecialDriverType)
    {
        SP_DEVINSTALL_PARAMS DeviceInstallParams;

        DeviceInstallParams.cbSize = sizeof(DeviceInstallParams);
        SetupDiGetDeviceInstallParams(DeviceInfoSet, DeviceInfoData, &DeviceInstallParams);
        DeviceInstallParams.Flags |= DI_RESOURCEPAGE_ADDED | DI_DRIVERPAGE_ADDED; // | DI_GENERALPAGE_ADDED;
        SetupDiSetDeviceInstallParams(DeviceInfoSet, DeviceInfoData, &DeviceInstallParams);
        AddSpecialPropertyPage(SpecialDriverType, AddFunc, Lparam);
        return TRUE;
    }

    if (AddCDROMPropertyPage(DeviceInfoSet,DeviceInfoData, AddFunc, Lparam))
    {
        return TRUE;
    }

    // Open device reg key to see if this is a WDM driver
    hkDrv = SetupDiOpenDevRegKey(DeviceInfoSet,
                                 DeviceInfoData,
                                 DICS_FLAG_GLOBAL,
                                 0,
                                 DIREG_DRV,
                                 KEY_ALL_ACCESS);
    if (!hkDrv)
        return FALSE;

    // Allocate and zero out memory for the struct that will contain page specific data
    Params = (POUR_PROP_PARAMS)LocalAlloc(LPTR, sizeof(OUR_PROP_PARAMS));
    if (!Params)
    {
        RegCloseKey(hkDrv);
        return FALSE;
    }

    // Initialize Params structure
    Params->DeviceInfoSet  = DeviceInfoSet;
    Params->DeviceInfoData = DeviceInfoData;
    Params->hkDrv          = hkDrv;

    // Override Resource page if this is not a WDM (PNP) driver
    DmOverrideResourcesPage(Info, AddFunc, Lparam, Params);

    // Try a couple of things to see if there are actually any drivers under this key
    // and cache the results

    // Try to open up the Drivers subkey
    if (RegOpenKey(Params->hkDrv, TEXT("Drivers"), &hkDrivers))
    {
        RegCloseKey(hkDrv);
        LocalFree(Params);
        return TRUE;
    }

    // Try to read the SubClasses key to determine which subclasses to process
    cbLen=sizeof(Params->szSubClasses);
    if (RegQueryValueEx(hkDrivers, TEXT("Subclasses"), NULL, NULL, (LPBYTE)Params->szSubClasses, &cbLen))
    {
        RegCloseKey(hkDrv);
        RegCloseKey(hkDrivers);
        LocalFree(Params);
        return TRUE;
    }

    Params->hkDrivers      = hkDrivers;

    // Initialize the property sheet page
    psp.dwSize      = sizeof(PROPSHEETPAGE);
    psp.dwFlags     = PSP_USECALLBACK; // | PSP_HASHELP;
    psp.hInstance   = ghInstance;
    psp.pszTemplate = MAKEINTRESOURCE(DLG_DM_ADVDLG);
    psp.pfnDlgProc  = DmAdvPropPageDlgProc;     // dlg window proc
    psp.lParam      = (LPARAM) Params;
    psp.pfnCallback = DmAdvPropPageDlgCallback; // control callback of the dlg window proc

    // Create the page & get back a handle
    hpsp = CreatePropertySheetPage(&psp);
    if (!hpsp)
    {
        RegCloseKey(hkDrv);
        LocalFree(Params);
        return FALSE;
    }

    // Add the property page
    if (!(*AddFunc)(hpsp, Lparam))
    {
        DestroyPropertySheetPage(hpsp);
        return FALSE;
    }

    return TRUE;
} /* DmAdvPropPageProvider */

UINT CALLBACK DmAdvPropPageDlgCallback(HWND hwnd,
                                       UINT uMsg,
                                       LPPROPSHEETPAGE ppsp)
{
    POUR_PROP_PARAMS Params;

    switch (uMsg)
    {
    case PSPCB_CREATE:  // This gets called when the page is created
        return TRUE;    // return TRUE to continue with creation of page

    case PSPCB_RELEASE: // This gets called when the page is destroyed
        Params = (POUR_PROP_PARAMS) ppsp->lParam;
        RegCloseKey(Params->hkDrv);
        RegCloseKey(Params->hkDrivers);
        LocalFree(Params);  // Free our local params

        return 0;       // return value ignored

    default:
        break;
    }

    return TRUE;
}

/*++

Routine Description: DmAdvPropPageDlgProc

    The windows control function for the Port Settings properties window

Arguments:

    hDlg, uMessage, wParam, lParam: standard windows DlgProc parameters

Return Value:

    BOOL: FALSE if function fails, TRUE if function passes

--*/
INT_PTR APIENTRY DmAdvPropPageDlgProc(IN HWND   hDlg,
                                      IN UINT   uMessage,
                                      IN WPARAM wParam,
                                      IN LPARAM lParam)
{
    switch (uMessage)
    {
    case WM_COMMAND:
        return DmAdvPropPage_OnCommand(hDlg, (int) LOWORD(wParam), (HWND)lParam, (UINT)HIWORD(wParam));

    case WM_CONTEXTMENU:
        return DmAdvPropPage_OnContextMenu((HWND)wParam, LOWORD(lParam), HIWORD(lParam));

    case WM_HELP:
        return DmAdvPropPage_OnHelp(hDlg, (LPHELPINFO) lParam);

    case WM_INITDIALOG:
        return DmAdvPropPage_OnInitDialog(hDlg, (HWND)wParam, lParam);

    case WM_NOTIFY:
        return DmAdvPropPage_OnNotify(hDlg,  (NMHDR *)lParam);

    case WM_DESTROY:
        return DmAdvPropPage_OnDestroy(hDlg, (NMHDR *)lParam);
    }

    return FALSE;
} /* DmAdvPropPageDlgProc */

BOOL DmAdvPropPage_OnCommand(
                            HWND ParentHwnd,
                            int  ControlId,
                            HWND ControlHwnd,
                            UINT NotifyCode
                            )
{
    POUR_PROP_PARAMS params =
        (POUR_PROP_PARAMS) GetWindowLongPtr(ParentHwnd, DWLP_USER);

    switch (ControlId)
    {
    case ID_ADV_PROP:
        DmAdvPropPage_OnPropertiesClicked(ParentHwnd, params);
        break;
    }

    return FALSE;
}

BOOL DmAdvPropPage_OnContextMenu(
                                HWND HwndControl,
                                WORD Xpos,
                                WORD Ypos
                                )
{
    WinHelp(HwndControl,
            gszWindowsHlp,
            HELP_CONTEXTMENU,
            (ULONG_PTR) aDMPropHelpIds);
    return FALSE;
}

BOOL DmAdvPropPage_OnHelp(
                         HWND       ParentHwnd,
                         LPHELPINFO HelpInfo
                         )
{
    if (HelpInfo->iContextType == HELPINFO_WINDOW)
    {
        WinHelp((HWND) HelpInfo->hItemHandle,
                gszWindowsHlp,
                HELP_WM_HELP,
                (ULONG_PTR) aDMPropHelpIds);
    }
    return FALSE;
}

BOOL DmAdvPropPage_OnInitDialog(
                               HWND    ParentHwnd,
                               HWND    FocusHwnd,
                               LPARAM  Lparam
                               )
{
    HWND hwndTree;
    POUR_PROP_PARAMS Params;
    HCURSOR hCursor;
    BOOL bSuccess;

    // on WM_INITDIALOG call, lParam points to the property sheet page.
    //
    // The lParam field in the property sheet page struct is set by the
    // caller. When I created the property sheet, I passed in a pointer
    // to a struct containing information about the device. Save this in
    // the user window long so I can access it on later messages.
    Params = (POUR_PROP_PARAMS) ((LPPROPSHEETPAGE)Lparam)->lParam;
    SetWindowLongPtr(ParentHwnd, DWLP_USER, (ULONG_PTR) Params);

    // Put up the wait cursor
    hCursor = SetCursor(LoadCursor(NULL,IDC_WAIT));

    //create the device tree.
    hwndTree = GetDlgItem(ParentHwnd, IDC_ADV_TREE);

    // Initialize the tree
    bSuccess = DmInitDeviceTree(hwndTree, Params);

    // Enable the adv properties button
    EnableWindow(GetDlgItem(ParentHwnd, ID_ADV_PROP), TRUE);

    // Tear down the wait cursor
    SetCursor(hCursor);

    return bSuccess;
}

BOOL DmAdvPropPage_OnNotify(
                           HWND    ParentHwnd,
                           LPNMHDR NmHdr
                           )
{
    POUR_PROP_PARAMS Params = (POUR_PROP_PARAMS) GetWindowLongPtr(ParentHwnd, DWLP_USER);

    switch (NmHdr->code)
    {
    case PSN_APPLY:    // Sent when the user clicks on Apply OR OK !!
        SetWindowLongPtr(ParentHwnd, DWLP_MSGRESULT, (LONG_PTR)PSNRET_NOERROR);
        return TRUE;

    case TVN_SELCHANGED:
        //Don't bother if we are closing. This helps avoid irritating
        //redraw problems as destroy causes several of these messages to be sent.
        if (!Params->bClosing)
        {
            LPNM_TREEVIEW   lpnmtv;
            TV_ITEM         tvi;
            PDMTREE_NODE    pTreeNode;
            BOOL            fEnablePropButton;
            HWND            hwndProp;

            lpnmtv    = (LPNM_TREEVIEW)NmHdr;
            tvi       = lpnmtv->itemNew;
            pTreeNode = (PDMTREE_NODE)tvi.lParam;
            fEnablePropButton = pTreeNode->pfnQueryConfig(ParentHwnd, pTreeNode);

            //override the enabling for driver entries
            if ((pTreeNode->NodeType == NodeTypeDriver) && (pTreeNode->dc != dcINVALID))
            {
                fEnablePropButton = TRUE;
            }

            // Enable or disable the Properties button depending upon
            // whether this driver can be configured
            hwndProp  = GetDlgItem(ParentHwnd, ID_ADV_PROP);
            EnableWindow(hwndProp, fEnablePropButton);
        }
        break;

    case NM_DBLCLK:
        //show properties on a double-click
        if (NmHdr->idFrom == (DWORD)IDC_ADV_TREE)
        {
            HWND            hwndTree;
            TV_HITTESTINFO  tvht;

            hwndTree = GetDlgItem(ParentHwnd, IDC_ADV_TREE);

            // Find out which tree item the cursor is on and call properties on it
            GetCursorPos(&tvht.pt);
            ScreenToClient(hwndTree, &tvht.pt);
            TreeView_HitTest(hwndTree, &tvht);
            if (tvht.flags & TVHT_ONITEM)
            {
                DoProperties(ParentHwnd, hwndTree, tvht.hItem);
            }
        }
        break;

#if 0 // stolen from Win98, not integrated yet
    case PSN_KILLACTIVE:
        FORWARD_WM_COMMAND(hDlg, IDOK, 0, 0, SendMessage);
        break;

    case PSN_APPLY:
        FORWARD_WM_COMMAND(hDlg, ID_APPLY, 0, 0, SendMessage);
        break;

    case PSN_SETACTIVE:
        FORWARD_WM_COMMAND(hDlg, ID_INIT, 0, 0, SendMessage);
        break;

    case PSN_RESET:
        FORWARD_WM_COMMAND(hDlg, IDCANCEL, 0, 0, SendMessage);
        break;

    case TVN_ITEMEXPANDING:
        {
            TV_ITEM tvi;
            HWND hwndTree = GetDlgItem(hDlg,IDC_ADV_TREE);

            tvi = lpnmtv->itemNew;
            tvi.mask = TVIF_PARAM;
            if (!TreeView_GetItem(hwndTree, &tvi))
                break;

            if (!tvi.lParam || IsBadReadPtr((LPVOID)tvi.lParam, 2))
            {
                DPF("****TVN_ITEMEXPANDING: lParam = 0 || BadReadPtr***\r\n");
                break;
            }
            if (*((short *)(tvi.lParam)) == 1)
            {
                //re-enum ACM codecs on expand because their states could have been programmatically changed.
                PCLASSNODE     pcn = (PCLASSNODE)(tvi.lParam);

                if (lpnmtv->action == TVE_EXPAND && !lstrcmpi(pcn->szClass, ACM))
                {
                    if (gfLoadedACM)
                        ACMNodeChange(hDlg);
                }
            }
            else if (!tvi.lParam && lpnmtv->action == TVE_COLLAPSE)
            {
                //dont let the root collapse.
                SetWindowLongPtr(hDlg, DWLP_MSGRESULT, (LPARAM)(LRESULT)TRUE);
                return TRUE;
            }
            break;
        }


    case TVN_BEGINLABELEDIT:
        //we don't want to allow editing of label unless the user explicitly wants it by
        //clicking context menu item
        if (!gfEditLabel)
            SetWindowLongPtr(hDlg, DWLP_MSGRESULT, (LPARAM)(LRESULT)TRUE);
        return TRUE;

    case TVN_ENDLABELEDIT:
        {
            HWND hwndTree;
            LPSTR pszFriendlyName = ((TV_DISPINFO *) lpnm)->item.pszText;
            TV_ITEM item;
            HTREEITEM hti;
            PIRESOURCE pIResource;
            char szWarn[128];
            char ach[MAXSTR];

            //user has chosen a new friendly name. COnfirm with the user and put it in the
            //registry. ALso unhook KB hook which was used to track Esc and Return
            if (gfnKBHook)
            {
                UnhookWindowsHookEx(gfnKBHook);
                gfnKBHook = NULL;
            }
            if (!pszFriendlyName)
                return FALSE;
            hwndTree = GetDlgItem(hDlg, IDC_ADV_TREE);
            hti = TreeView_GetSelection(hwndTree);
            item.hItem =  hti;
            item.mask = TVIF_PARAM;
            TreeView_GetItem(hwndTree, &item);

            LoadString(ghInstance, IDS_FRIENDLYWARNING, szWarn, sizeof(szWarn));
            wsprintf(ach, szWarn, pszFriendlyName);
            LoadString(ghInstance, IDS_FRIENDLYNAME, szWarn, sizeof(szWarn));
            if (mmse_MessageBox(hDlg, ach, szWarn, MMSE_YESNO) == MMSE_NO)
            {
                SetFocus(hwndTree);
                return FALSE;
            }
            if (*((short *)(item.lParam)) == 2)
            {
                pIResource = (PIRESOURCE)item.lParam;
                lstrcpy(pIResource->szFriendlyName, pszFriendlyName);
                SaveDevFriendlyName(pIResource);
            }
            else
            {
                PINSTRUMENT pInstr = (PINSTRUMENT)item.lParam;
                lstrcpy(pInstr->szFriendlyName, pszFriendlyName);
                SaveInstrFriendlyName(pInstr);
            }
            SetWindowLongPtr(hDlg, DWLP_MSGRESULT, (LPARAM)(LRESULT)TRUE);
            return TRUE;
        }
    case NM_RCLICK:
        //popup context menu.
        TreeContextMenu(hDlg,  GetDlgItem(hDlg, IDC_ADV_TREE));
        return TRUE;

#endif
    default:
        return FALSE;
    }

    return FALSE;
}


void DoProperties(HWND ParentHwnd, HWND hWndI, HTREEITEM htiCur)
{
    TV_ITEM      tvi;
    PDMTREE_NODE pTreeNode;
    BOOL         bRestart;

    // Get item struct attached to selected node
    tvi.mask = TVIF_PARAM;
    tvi.hItem = htiCur;
    if (TreeView_GetItem (hWndI, &tvi))
    {
        // Get my private data structure from item struct
        pTreeNode = (PDMTREE_NODE)tvi.lParam;

        if (pTreeNode->NodeType != NodeTypeDriver)
        {
            if (!pTreeNode->pfnQueryConfig(ParentHwnd, pTreeNode))
            {
                return;
            }
        }

        // Call config and get back whether restart needed
        pTreeNode->hti = htiCur; //this allows us to work with the legacy MIDI setup code
        bRestart = pTreeNode->pfnConfig(ParentHwnd, pTreeNode);

        if (bRestart)
        {
            PropSheet_Changed(GetParent(ParentHwnd), ParentHwnd);
        }
    }

    return;
}

void DmAdvPropPage_OnPropertiesClicked(
                                      HWND             ParentHwnd,
                                      POUR_PROP_PARAMS Params
                                      )
{
    HWND         hWndI;
    HTREEITEM    htiCur;

    // Get handle to treeview control
    hWndI  = GetDlgItem(ParentHwnd, IDC_ADV_TREE);

    // Get handle to currently selected node
    htiCur = TreeView_GetSelection (hWndI);

    if (htiCur != NULL)
    {
        DoProperties(ParentHwnd, hWndI, htiCur);
    }

    return;
}

INT_PTR APIENTRY DmResourcesPageDlgProc(IN HWND   hDlg,
                                        IN UINT   uMessage,
                                        IN WPARAM wParam,
                                        IN LPARAM lParam)
{
    return FALSE;
} /* DmAdvPropPageDlgProc */



INT_PTR CALLBACK CDDlg(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);


BOOL AddCDROMPropertyPage( HDEVINFO             hDeviceInfoSet,
                           PSP_DEVINFO_DATA     pDeviceInfoData,
                           LPFNADDPROPSHEETPAGE AddFunc,
                           LPARAM               Lparam
                          )
{
    BOOL fHandled = FALSE;

    if (IsEqualGUID(&pDeviceInfoData->ClassGuid,&GUID_DEVCLASS_CDROM))
    {
        PROPSHEETPAGE    psp;
        HPROPSHEETPAGE   hpsp;
        PALLDEVINFO      padi;

        padi = GlobalAllocPtr(GHND, sizeof(ALLDEVINFO));

		if (!padi) return FALSE;

        padi->hDevInfo = hDeviceInfoSet;
        padi->pDevInfoData = pDeviceInfoData;

        // Add our own page for DLG_DM_LEGACY_RESOURCES
        // Initialize the property sheet page
        psp.dwSize      = sizeof(PROPSHEETPAGE);
        psp.dwFlags     = 0;
        psp.hInstance   = ghInstance;
        psp.pszTemplate = MAKEINTRESOURCE(DM_CDDLG);
        psp.pfnDlgProc  = CDDlg;                       // dlg window proc
        psp.lParam      = (LPARAM) padi;
        psp.pfnCallback = 0;                          // control callback of the dlg window proc

        // Create the page & get back a handle
        hpsp = CreatePropertySheetPage(&psp);
        if (!hpsp)
        {
            fHandled = TRUE;
        }
        else if (!(*AddFunc)(hpsp, Lparam))
        {
            GlobalFreePtr(padi);
            DestroyPropertySheetPage(hpsp);
            fHandled = FALSE;
        }
    }

    return(fHandled);
}

INT_PTR CALLBACK AdvDlg(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);



BOOL AddSpecialPropertyPage( DWORD                SpecialDriverType,
                             LPFNADDPROPSHEETPAGE AddFunc,
                             LPARAM               Lparam
                            )
{
    PROPSHEETPAGE    psp;
    HPROPSHEETPAGE   hpsp;

    // Add our own page for DLG_DM_LEGACY_RESOURCES
    // Initialize the property sheet page
    psp.dwSize      = sizeof(PROPSHEETPAGE);
    psp.dwFlags     = 0;
    psp.hInstance   = ghInstance;
    psp.pszTemplate = MAKEINTRESOURCE(DM_ADVDLG);
    psp.pfnDlgProc  = AdvDlg;                       // dlg window proc
    psp.lParam      = (LPARAM)SpecialDriverType;
    psp.pfnCallback = 0;                          // control callback of the dlg window proc

    // Create the page & get back a handle
    hpsp = CreatePropertySheetPage(&psp);
    if (!hpsp)
    {
        return FALSE;
    }

    // Add the property page
    if (!(*AddFunc)(hpsp, Lparam))
    {
        DestroyPropertySheetPage(hpsp);
        return FALSE;
    }

    return TRUE;
}



BOOL DmOverrideResourcesPage(LPVOID        Info,
                             LPFNADDPROPSHEETPAGE AddFunc,
                             LPARAM               Lparam,
                             POUR_PROP_PARAMS     Params
                            )
{
    HKEY             hkDrv;
    HDEVINFO         DeviceInfoSet;
    PSP_DEVINFO_DATA DeviceInfoData;
    SP_DEVINSTALL_PARAMS DeviceInstallParams;
    PROPSHEETPAGE    psp;
    HPROPSHEETPAGE            hpsp;

    TCHAR szDriverType[16];
    DWORD cbLen;

    hkDrv          = Params->hkDrv;
    DeviceInfoSet  = Params->DeviceInfoSet;
    DeviceInfoData = Params->DeviceInfoData;

    // Query value of DriverType field to decide if this is a WDM driver
    cbLen = sizeof(szDriverType);
    if (!RegQueryValueEx(hkDrv, TEXT("DriverType"), NULL, NULL, (LPBYTE)szDriverType, &cbLen))
    {
        if ( lstrcmpi(szDriverType,TEXT("Legacy")) || lstrcmpi(szDriverType,TEXT("PNPISA")) )
        {
            // This is a PNPISA or Legacy device. Override resource page.
            DeviceInstallParams.cbSize = sizeof(SP_DEVINSTALL_PARAMS);
            SetupDiGetDeviceInstallParams(DeviceInfoSet, DeviceInfoData, &DeviceInstallParams);
            DeviceInstallParams.Flags |= DI_RESOURCEPAGE_ADDED;
            SetupDiSetDeviceInstallParams(DeviceInfoSet, DeviceInfoData, &DeviceInstallParams);

            // Add our own page for DLG_DM_LEGACY_RESOURCES
            // Initialize the property sheet page
            psp.dwSize      = sizeof(PROPSHEETPAGE);
            psp.dwFlags     = 0;
            psp.hInstance   = ghInstance;
            psp.pszTemplate = MAKEINTRESOURCE(DLG_DM_LEGACY_RESOURCES);
            psp.pfnDlgProc  = DmResourcesPageDlgProc;     // dlg window proc
            psp.lParam      = (LPARAM)0;
            psp.pfnCallback = 0;                          // control callback of the dlg window proc

            // Create the page & get back a handle
            hpsp = CreatePropertySheetPage(&psp);
            if (!hpsp)
            {
                return FALSE;
            }

            // Add the property page
            if (!(*AddFunc)(hpsp, Lparam))
            {
                DestroyPropertySheetPage(hpsp);
                return FALSE;
            }

        }
    }

    return TRUE;
}

/*
 ***************************************************************
 * BOOL DmInitDeviceTree
 *
 *      This function calls commctrl to create the image list and tree and
 *      the opens the registry, reads each class and loads all devices under
 *      the class by calling ReadNodes. For ACM however it uses ACM
 *      APIs (this enumeration code is in msacmcpl.c)
 ***************************************************************
 */
BOOL DmInitDeviceTree(HWND hwndTree, POUR_PROP_PARAMS Params)
{
    TV_INSERTSTRUCT ti;

    HDEVINFO         DeviceInfoSet;
    PSP_DEVINFO_DATA DeviceInfoData;

    TCHAR *strtok_State;         // strtok state
    TCHAR *pszClass;             // Information about e.g. classguid\0000\Drivers\wave
    HKEY hkClass;

    DWORD idxR3DriverName;      // Information about e.g. classguid\0000\Drivers\wave\foo.drv
    HKEY hkR3DriverName;
    TCHAR szR3DriverName[64];

    DWORD cbLen;

    PDMTREE_NODE pTreeNode;

    SUBTYPE_INFO *pSubtypeInfo;

    HTREEITEM htiRoot;
    HTREEITEM htiClass;
    HTREEITEM htiDriver;

    // Load up all the class descriptions and icons
    LoadSubtypeInfo(hwndTree);

    // Clear out the tree
    SendMessage(hwndTree, WM_SETREDRAW, FALSE, 0L);

    // Allocate my private data structure for this class
    pTreeNode = (PDMTREE_NODE)LocalAlloc(LPTR, sizeof(DMTREE_NODE));
    if (!pTreeNode)
    {
        return FALSE;
    }

    pSubtypeInfo = GetSubtypeInfo(NULL);

    pTreeNode->NodeType = NodeTypeRoot;
    pTreeNode->pfnConfig = pSubtypeInfo->pfnConfig;
    pTreeNode->pfnQueryConfig = pSubtypeInfo->pfnQueryConfig;

    // Insert root entry
    ti.hParent = TVI_ROOT;
    ti.hInsertAfter = TVI_LAST;
    ti.item.mask = TVIF_TEXT | TVIF_PARAM | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
    ti.item.iImage = ti.item.iSelectedImage = pSubtypeInfo->IconIndex;
    ti.item.pszText = pSubtypeInfo->szDescription;
    ti.item.lParam = (LPARAM)pTreeNode;
    htiRoot = NULL; //TreeView_InsertItem(hwndTree, &ti);

    // Enumerate all the subclasses
    for (
        pszClass = mystrtok(Params->szSubClasses,NULL,&strtok_State);
        pszClass;
        pszClass = mystrtok(NULL,NULL,&strtok_State)
        )
    {

        // Get an ID for this class
        pSubtypeInfo = GetSubtypeInfo(pszClass);

        // Open up each subclass
        if (RegOpenKey(Params->hkDrivers, pszClass, &hkClass))
        {
            continue;
        }

        // Allocate my private data structure for this class
        pTreeNode = (PDMTREE_NODE)LocalAlloc(LPTR, sizeof(DMTREE_NODE));
        if (!pTreeNode)
        {
            RegCloseKey(hkClass);
            continue;
        }

        pTreeNode->NodeType = NodeTypeClass;
        pTreeNode->pfnConfig = pSubtypeInfo->pfnConfig;
        pTreeNode->pfnQueryConfig = pSubtypeInfo->pfnQueryConfig;

        // Initialize tree insert struct
        ti.hParent = htiRoot;
        ti.hInsertAfter = TVI_LAST;
        ti.item.mask = TVIF_TEXT | TVIF_PARAM | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
        ti.item.iImage = ti.item.iSelectedImage = pSubtypeInfo->IconIndex;
        ti.item.pszText = pSubtypeInfo->szDescription;
        ti.item.lParam = (LPARAM)pTreeNode;

        // Insert Class entry into tree
        htiClass = TreeView_InsertItem(hwndTree, &ti);

        // Under each class is a set of driver name subkeys.
        // For each driver (e.g. foo1.drv, foo2.drv, etc.)
        for (idxR3DriverName = 0;
            !RegEnumKey(hkClass, idxR3DriverName, szR3DriverName, sizeof(szR3DriverName)/sizeof(TCHAR));
            idxR3DriverName++)
        {

            // Open the key to the driver name
            if (RegOpenKey(hkClass, szR3DriverName, &hkR3DriverName))
            {
                continue;
            }

            // Create the branch for this subclass
            pTreeNode = (PDMTREE_NODE)LocalAlloc(LPTR, sizeof(DMTREE_NODE));
            pTreeNode->NodeType = NodeTypeDriver;
            pTreeNode->pfnConfig = pSubtypeInfo->pfnConfig;
            pTreeNode->pfnQueryConfig = pSubtypeInfo->pfnQueryConfig;
            pTreeNode->dc = pSubtypeInfo->dc;

            // Get driver name
            cbLen = sizeof(pTreeNode->szDriver);
            RegQueryValueEx(hkR3DriverName, TEXT("Driver"), NULL, NULL, (LPBYTE)pTreeNode->szDriver, &cbLen);
            wcscpy(pTreeNode->wszDriver, pTreeNode->szDriver);

            // Get driver description
            cbLen = sizeof(pTreeNode->szDescription);
            RegQueryValueEx(hkR3DriverName, TEXT("Description"), NULL, NULL, (LPBYTE)pTreeNode->szDescription, &cbLen);

            // Get driver alias
            cbLen = sizeof(pTreeNode->szAlias);
            RegQueryValueEx(hkR3DriverName, TEXT("Alias"), NULL, NULL, (LPBYTE)pTreeNode->szAlias, &cbLen);
            wcscpy(pTreeNode->wszAlias, pTreeNode->szAlias);

            // Insert Class entry
            ti.hParent = htiClass;
            ti.hInsertAfter = TVI_LAST;
            ti.item.mask = TVIF_TEXT | TVIF_PARAM | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
            ti.item.iImage = ti.item.iSelectedImage = pSubtypeInfo->IconIndex;
            ti.item.pszText = pTreeNode->szDescription;
            ti.item.lParam = (LPARAM)pTreeNode;

            htiDriver = TreeView_InsertItem(hwndTree, &ti);

            // Close the Driver Name key
            RegCloseKey(hkR3DriverName);
        }
        // Close the class key
        RegCloseKey(hkClass);
    }

    // Open up the tree and display
    TreeView_Expand(hwndTree, htiRoot, TVE_EXPAND);
    SendMessage(hwndTree, WM_SETREDRAW, TRUE, 0L);

    return TRUE;
}


// Free up the tree
void DmFreeAdvDlgTree (HWND hTree, HTREEITEM hti)
{
    HTREEITEM htiChild;
    TV_ITEM tvi;

    // Delete all children by calling myself recursively
    while ((htiChild = TreeView_GetChild(hTree, hti)) != NULL)
    {
        DmFreeAdvDlgTree(hTree, htiChild);
    }

    if (hti!=TVI_ROOT)
    {
        // Delete my attached data structures
        tvi.mask = TVIF_PARAM;
        tvi.hItem = hti;
        tvi.lParam = 0;
        TreeView_GetItem(hTree, &tvi);
        if (tvi.lParam != 0)
            LocalFree ((HANDLE)tvi.lParam);

        // Delete myself
        TreeView_DeleteItem (hTree, hti);
    }

    return;
}

BOOL DmAdvPropPage_OnDestroy(
                            HWND    ParentHwnd,
                            LPNMHDR NmHdr
                            )
{
    HWND hTree;
    HIMAGELIST hImageList;
    POUR_PROP_PARAMS Params = (POUR_PROP_PARAMS) GetWindowLongPtr(ParentHwnd, DWLP_USER);

    if (Params)
    {
        Params->bClosing = TRUE;    // Remember that we're now closing
    }

    // Get handle to treeview control
    hTree = GetDlgItem(ParentHwnd, IDC_ADV_TREE);

    // Free all the entries on the control
    DmFreeAdvDlgTree(hTree,TVI_ROOT);

    // Free up the image list attached to the control
    hImageList = TreeView_GetImageList(hTree, TVSIL_NORMAL);
    if (hImageList)
    {
        TreeView_SetImageList(hTree, NULL, TVSIL_NORMAL);
        ImageList_Destroy (hImageList);
    }

    return FALSE;
}


