/*
 * New device installer (newdev.dll)
 *
 * Copyright 2006 Hervé Poussineau (hpoussin@reactos.org)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "newdev_private.h"

#include <wincon.h>
#include <cfgmgr32.h>
#include <shlobj.h>

HANDLE hThread;

static VOID
CenterWindow(
    IN HWND hWnd)
{
    HWND hWndParent;
    RECT rcParent;
    RECT rcWindow;

    hWndParent = GetParent(hWnd);
    if (hWndParent == NULL)
        hWndParent = GetDesktopWindow();

    GetWindowRect(hWndParent, &rcParent);
    GetWindowRect(hWnd, &rcWindow);

    SetWindowPos(
        hWnd,
        HWND_TOP,
        ((rcParent.right - rcParent.left) - (rcWindow.right - rcWindow.left)) / 2,
        ((rcParent.bottom - rcParent.top) - (rcWindow.bottom - rcWindow.top)) / 2,
        0,
        0,
        SWP_NOSIZE);
}

static BOOL
CanDisableDevice(
    IN DEVINST DevInst,
    IN HMACHINE hMachine,
    OUT BOOL *CanDisable)
{
    CONFIGRET cr;
    ULONG Status, ProblemNumber;
    BOOL Ret = FALSE;

    cr = CM_Get_DevNode_Status_Ex(&Status,
                                  &ProblemNumber,
                                  DevInst,
                                  0,
                                  hMachine);
    if (cr == CR_SUCCESS)
    {
        *CanDisable = ((Status & DN_DISABLEABLE) != 0);
        Ret = TRUE;
    }

    return Ret;
}

static BOOL
IsDeviceStarted(
    IN DEVINST DevInst,
    IN HMACHINE hMachine,
    OUT BOOL *IsEnabled)
{
    CONFIGRET cr;
    ULONG Status, ProblemNumber;
    BOOL Ret = FALSE;

    cr = CM_Get_DevNode_Status_Ex(
        &Status,
        &ProblemNumber,
        DevInst,
        0,
        hMachine);
    if (cr == CR_SUCCESS)
    {
        *IsEnabled = ((Status & DN_STARTED) != 0);
    Ret = TRUE;
    }

    return Ret;
}

static BOOL
StartDevice(
    IN HDEVINFO DeviceInfoSet,
    IN PSP_DEVINFO_DATA DevInfoData OPTIONAL,
    IN BOOL bEnable,
    IN DWORD HardwareProfile OPTIONAL,
    OUT BOOL *bNeedReboot OPTIONAL)
{
    SP_PROPCHANGE_PARAMS pcp;
    SP_DEVINSTALL_PARAMS dp;
    DWORD LastErr;
    BOOL Ret = FALSE;

    pcp.ClassInstallHeader.cbSize = sizeof(SP_CLASSINSTALL_HEADER);
    pcp.ClassInstallHeader.InstallFunction = DIF_PROPERTYCHANGE;
    pcp.HwProfile = HardwareProfile;

    if (bEnable)
    {
        /* try to enable the device on the global profile */
        pcp.StateChange = DICS_ENABLE;
        pcp.Scope = DICS_FLAG_GLOBAL;

        /* ignore errors */
        LastErr = GetLastError();
        if (SetupDiSetClassInstallParams(
            DeviceInfoSet,
            DevInfoData,
            &pcp.ClassInstallHeader,
            sizeof(SP_PROPCHANGE_PARAMS)))
        {
            SetupDiCallClassInstaller(
                DIF_PROPERTYCHANGE,
                DeviceInfoSet,
                DevInfoData);
        }
        SetLastError(LastErr);
    }

    /* try config-specific */
    pcp.StateChange = (bEnable ? DICS_ENABLE : DICS_DISABLE);
    pcp.Scope = DICS_FLAG_CONFIGSPECIFIC;

    if (SetupDiSetClassInstallParams(
            DeviceInfoSet,
            DevInfoData,
            &pcp.ClassInstallHeader,
            sizeof(SP_PROPCHANGE_PARAMS)) &&
        SetupDiCallClassInstaller(DIF_PROPERTYCHANGE,
            DeviceInfoSet,
            DevInfoData))
    {
        dp.cbSize = sizeof(SP_DEVINSTALL_PARAMS);
        if (SetupDiGetDeviceInstallParams(
            DeviceInfoSet,
            DevInfoData,
            &dp))
        {
            if (bNeedReboot != NULL)
            {
                *bNeedReboot = ((dp.Flags & (DI_NEEDRESTART | DI_NEEDREBOOT)) != 0);
            }

            Ret = TRUE;
        }
    }
    return Ret;
}

static DWORD WINAPI
FindDriverProc(
    IN LPVOID lpParam)
{
    PDEVINSTDATA DevInstData;
    DWORD config_flags;
    BOOL result = FALSE;

    DevInstData = (PDEVINSTDATA)lpParam;

    result = ScanFoldersForDriver(DevInstData);

    if (result)
    {
        PostMessage(DevInstData->hDialog, WM_SEARCH_FINISHED, 1, 0);
    }
    else
    {
        /* Update device configuration */
        if (SetupDiGetDeviceRegistryProperty(
            DevInstData->hDevInfo,
            &DevInstData->devInfoData,
            SPDRP_CONFIGFLAGS,
            NULL,
            (BYTE *)&config_flags,
            sizeof(config_flags),
            NULL))
        {
            config_flags |= CONFIGFLAG_FAILEDINSTALL;
            SetupDiSetDeviceRegistryPropertyW(
                DevInstData->hDevInfo,
                &DevInstData->devInfoData,
                SPDRP_CONFIGFLAGS,
                (BYTE *)&config_flags, sizeof(config_flags));
        }

        PostMessage(DevInstData->hDialog, WM_SEARCH_FINISHED, 0, 0);
    }
    return 0;
}

static DWORD WINAPI
InstallDriverProc(
    IN LPVOID lpParam)
{
    PDEVINSTDATA DevInstData;
    BOOL res;

    DevInstData = (PDEVINSTDATA)lpParam;
    res = InstallCurrentDriver(DevInstData);
    PostMessage(DevInstData->hDialog, WM_INSTALL_FINISHED, res ? 0 : 1, 0);
    return 0;
}

static VOID
PopulateCustomPathCombo(
    IN HWND hwndCombo)
{
    HKEY hKey = NULL;
    DWORD dwRegType;
    DWORD dwPathLength = 0;
    LPWSTR Buffer = NULL;
    LPCWSTR Path;
    LONG rc;

    (void)ComboBox_ResetContent(hwndCombo);

    /* RegGetValue would have been better... */
    rc = RegOpenKeyEx(
        HKEY_LOCAL_MACHINE,
        REGSTR_PATH_SETUP REGSTR_KEY_SETUP,
        0,
        KEY_QUERY_VALUE,
        &hKey);
    if (rc != ERROR_SUCCESS)
    {
        TRACE("RegOpenKeyEx() failed with error 0x%lx\n", rc);
        goto cleanup;
    }
    rc = RegQueryValueExW(
        hKey,
        L"Installation Sources",
        NULL,
        &dwRegType,
        NULL,
        &dwPathLength);
    if (rc != ERROR_SUCCESS || dwRegType != REG_MULTI_SZ)
    {
        TRACE("RegQueryValueEx() failed with error 0x%lx\n", rc);
        goto cleanup;
    }

    /* Allocate enough space to add 2 NULL chars at the end of the string */
    Buffer = HeapAlloc(GetProcessHeap(), 0, dwPathLength + 2 * sizeof(WCHAR));
    if (!Buffer)
    {
        TRACE("HeapAlloc() failed\n");
        goto cleanup;
    }
    rc = RegQueryValueExW(
        hKey,
        L"Installation Sources",
        NULL,
        NULL,
        (LPBYTE)Buffer,
        &dwPathLength);
    if (rc != ERROR_SUCCESS)
    {
        TRACE("RegQueryValueEx() failed with error 0x%lx\n", rc);
        goto cleanup;
    }

    Buffer[dwPathLength / sizeof(WCHAR)] = UNICODE_NULL;
    Buffer[dwPathLength / sizeof(WCHAR) + 1] = UNICODE_NULL;

    /* Populate combo box */
    for (Path = Buffer; *Path; Path += wcslen(Path) + 1)
    {
        (void)ComboBox_AddString(hwndCombo, Path);
        if (Path == Buffer)
            (void)ComboBox_SetCurSel(hwndCombo, 0);
    }

cleanup:
    if (hKey != NULL)
        RegCloseKey(hKey);
    HeapFree(GetProcessHeap(), 0, Buffer);
}

static VOID
SaveCustomPath(
    IN HWND hwndCombo)
{
    LPWSTR CustomPath = NULL;
    DWORD CustomPathLength;
    LPWSTR Buffer = NULL;
    LPWSTR pBuffer; /* Pointer into Buffer */
    int ItemsCount, Length;
    int i;
    DWORD TotalLength = 0;
    BOOL UseCustomPath = TRUE;
    HKEY hKey = NULL;
    LONG rc;

    /* Get custom path */
    Length = ComboBox_GetTextLength(hwndCombo) + 1;
    CustomPath = HeapAlloc(GetProcessHeap(), 0, Length * sizeof(WCHAR));
    if (!CustomPath)
    {
        TRACE("HeapAlloc() failed\n");
        goto cleanup;
    }
    CustomPathLength = GetWindowTextW(hwndCombo, CustomPath, Length) + 1;

    /* Calculate length of the buffer */
    ItemsCount = ComboBox_GetCount(hwndCombo);
    if (ItemsCount == CB_ERR)
    {
        TRACE("ComboBox_GetCount() failed\n");
        goto cleanup;
    }
    for (i = 0; i < ItemsCount; i++)
    {
        Length = ComboBox_GetLBTextLen(hwndCombo, i);
        if (Length == CB_ERR)
        {
            TRACE("ComboBox_GetLBTextLen() failed\n");
            goto cleanup;
        }
        TotalLength += Length + 1;
    }
    TotalLength++; /* Final NULL char */

    /* Allocate buffer */
    Buffer = HeapAlloc(GetProcessHeap(), 0, (CustomPathLength + TotalLength + 1) * sizeof(WCHAR));
    if (!Buffer)
    {
        TRACE("HeapAlloc() failed\n");
        goto cleanup;
    }

    /* Fill the buffer */
    pBuffer = &Buffer[CustomPathLength];
    for (i = 0; i < ItemsCount; i++)
    {
        Length = ComboBox_GetLBText(hwndCombo, i, pBuffer);
        if (Length == CB_ERR)
        {
            TRACE("ComboBox_GetLBText() failed\n");
            goto cleanup;
        }
        else if (UseCustomPath && _wcsicmp(CustomPath, pBuffer) == 0)
            UseCustomPath = FALSE;
        pBuffer += 1 + Length;
    }
    *pBuffer = '\0'; /* Add final NULL char */

    if (!UseCustomPath)
    {
        /* Nothing to save to registry */
        goto cleanup;
    }

    TotalLength += CustomPathLength;
    wcscpy(Buffer, CustomPath);

    /* Save the buffer */
    /* RegSetKeyValue would have been better... */
    rc = RegOpenKeyEx(
        HKEY_LOCAL_MACHINE,
        REGSTR_PATH_SETUP REGSTR_KEY_SETUP,
        0,
        KEY_SET_VALUE,
        &hKey);
    if (rc != ERROR_SUCCESS)
    {
        TRACE("RegOpenKeyEx() failed with error 0x%lx\n", rc);
        goto cleanup;
    }
    rc = RegSetValueExW(
        hKey,
        L"Installation Sources",
        0,
        REG_MULTI_SZ,
        (const BYTE*)Buffer,
        TotalLength * sizeof(WCHAR));
    if (rc != ERROR_SUCCESS)
    {
        TRACE("RegSetValueEx() failed with error 0x%lx\n", rc);
        goto cleanup;
    }

cleanup:
    if (hKey != NULL)
        RegCloseKey(hKey);
    HeapFree(GetProcessHeap(), 0, CustomPath);
    HeapFree(GetProcessHeap(), 0, Buffer);
}

static INT_PTR CALLBACK
WelcomeDlgProc(
    IN HWND hwndDlg,
    IN UINT uMsg,
    IN WPARAM wParam,
    IN LPARAM lParam)
{
    PDEVINSTDATA DevInstData;
    UNREFERENCED_PARAMETER(wParam);

    /* Retrieve pointer to the global setup data */
    DevInstData = (PDEVINSTDATA)GetWindowLongPtr(hwndDlg, GWL_USERDATA);

    switch (uMsg)
    {
        case WM_INITDIALOG:
        {
            HWND hwndControl;
            DWORD dwStyle;

            /* Get pointer to the global setup data */
            DevInstData = (PDEVINSTDATA)((LPPROPSHEETPAGE)lParam)->lParam;
            SetWindowLongPtr(hwndDlg, GWL_USERDATA, (DWORD_PTR)DevInstData);

            hwndControl = GetParent(hwndDlg);

            /* Center the wizard window */
            CenterWindow(hwndControl);

            /* Hide the system menu */
            dwStyle = GetWindowLongPtr(hwndControl, GWL_STYLE);
            SetWindowLongPtr(hwndControl, GWL_STYLE, dwStyle & ~WS_SYSMENU);

            /* Set title font */
            SendDlgItemMessage(
                hwndDlg,
                IDC_WELCOMETITLE,
                WM_SETFONT,
                (WPARAM)DevInstData->hTitleFont,
                (LPARAM)TRUE);

            SendDlgItemMessage(
                hwndDlg,
                IDC_DEVICE,
                WM_SETTEXT,
                0,
                (LPARAM)DevInstData->buffer);

            SendDlgItemMessage(
                hwndDlg,
                IDC_RADIO_AUTO,
                BM_SETCHECK,
                (WPARAM)TRUE,
                (LPARAM)0);
            break;
        }

        case WM_NOTIFY:
        {
            LPNMHDR lpnm = (LPNMHDR)lParam;

            switch (lpnm->code)
            {
                case PSN_SETACTIVE:
                    /* Enable the Next button */
                    PropSheet_SetWizButtons(GetParent(hwndDlg), PSWIZB_NEXT);
                    break;

                case PSN_WIZNEXT:
                    /* Handle a Next button click, if necessary */
                    if (SendDlgItemMessage(hwndDlg, IDC_RADIO_AUTO, BM_GETCHECK, (WPARAM)0, (LPARAM)0) == BST_CHECKED)
                    {
                        if (PrepareFoldersToScan(DevInstData, TRUE, FALSE, NULL))
                            PropSheet_SetCurSelByID(GetParent(hwndDlg), IDD_SEARCHDRV);
                        else
                            PropSheet_SetCurSelByID(GetParent(hwndDlg), IDD_INSTALLFAILED);
                    }
                    return TRUE;

                default:
                    break;
            }
            break;
        }

        default:
            break;
    }

    return FALSE;
}

static void
IncludePath(HWND Dlg, BOOL Enabled)
{
    EnableWindow(GetDlgItem(Dlg, IDC_COMBO_PATH), Enabled);
    EnableWindow(GetDlgItem(Dlg, IDC_BROWSE), Enabled);
}

static void
AutoDriver(HWND Dlg, BOOL Enabled)
{
    EnableWindow(GetDlgItem(Dlg, IDC_CHECK_MEDIA), Enabled);
    EnableWindow(GetDlgItem(Dlg, IDC_CHECK_PATH), Enabled);
    IncludePath(Dlg, Enabled & IsDlgButtonChecked(Dlg, IDC_CHECK_PATH));
}

static INT_PTR CALLBACK
CHSourceDlgProc(
    IN HWND hwndDlg,
    IN UINT uMsg,
    IN WPARAM wParam,
    IN LPARAM lParam)
{
    PDEVINSTDATA DevInstData;

    /* Retrieve pointer to the global setup data */
    DevInstData = (PDEVINSTDATA)GetWindowLongPtr(hwndDlg, GWL_USERDATA);

    switch (uMsg)
    {
        case WM_INITDIALOG:
        {
            HWND hwndControl;
            DWORD dwStyle;

            /* Get pointer to the global setup data */
            DevInstData = (PDEVINSTDATA)((LPPROPSHEETPAGE)lParam)->lParam;
            SetWindowLongPtr(hwndDlg, GWL_USERDATA, (DWORD_PTR)DevInstData);

            hwndControl = GetParent(hwndDlg);

            /* Center the wizard window */
            CenterWindow(hwndControl);

            /* Hide the system menu */
            dwStyle = GetWindowLongPtr(hwndControl, GWL_STYLE);
            SetWindowLongPtr(hwndControl, GWL_STYLE, dwStyle & ~WS_SYSMENU);

            PopulateCustomPathCombo(GetDlgItem(hwndDlg, IDC_COMBO_PATH));

            SendDlgItemMessage(
                hwndDlg,
                IDC_RADIO_SEARCHHERE,
                BM_SETCHECK,
                (WPARAM)TRUE,
                (LPARAM)0);
            AutoDriver(hwndDlg, TRUE);
            IncludePath(hwndDlg, FALSE);

            /* Disable manual driver choice for now */
            EnableWindow(GetDlgItem(hwndDlg, IDC_RADIO_CHOOSE), FALSE);

            break;
        }

        case WM_COMMAND:
        {
            switch (LOWORD(wParam))
            {
                case IDC_RADIO_SEARCHHERE:
                    AutoDriver(hwndDlg, TRUE);
                    return TRUE;

                case IDC_RADIO_CHOOSE:
                    AutoDriver(hwndDlg, FALSE);
                    return TRUE;

                case IDC_CHECK_PATH:
                    IncludePath(hwndDlg, IsDlgButtonChecked(hwndDlg, IDC_CHECK_PATH));
                    return TRUE;

                case IDC_BROWSE:
                {
                    BROWSEINFO bi = { 0 };
                    LPITEMIDLIST pidl;

                    bi.hwndOwner = hwndDlg;
                    bi.ulFlags = BIF_RETURNONLYFSDIRS;
                    pidl = SHBrowseForFolder(&bi);
                    if (pidl)
                    {
                        WCHAR Directory[MAX_PATH];
                        IMalloc* malloc;

                        if (SHGetPathFromIDListW(pidl, Directory))
                        {
                            /* Set the IDC_COMBO_PATH text */
                            SetWindowTextW(GetDlgItem(hwndDlg, IDC_COMBO_PATH), Directory);
                        }

                        /* Free memory, if possible */
                        if (SUCCEEDED(SHGetMalloc(&malloc)))
                        {
                            IMalloc_Free(malloc, pidl);
                            IMalloc_Release(malloc);
                        }
                        return TRUE;
                    }
                    break;
                }
            }
            break;
        }

        case WM_NOTIFY:
        {
            LPNMHDR lpnm = (LPNMHDR)lParam;

            switch (lpnm->code)
            {
                case PSN_SETACTIVE:
                    /* Enable the Next and Back buttons */
                    PropSheet_SetWizButtons(GetParent(hwndDlg), PSWIZB_NEXT | PSWIZB_BACK);
                    break;

                case PSN_WIZNEXT:
                    /* Handle a Next button click, if necessary */
                    if (IsDlgButtonChecked(hwndDlg, IDC_RADIO_SEARCHHERE))
                    {
                        SaveCustomPath(GetDlgItem(hwndDlg, IDC_COMBO_PATH));
                        HeapFree(GetProcessHeap(), 0, DevInstData->CustomSearchPath);
                        DevInstData->CustomSearchPath = NULL;
                        if (PrepareFoldersToScan(
                            DevInstData,
                            IsDlgButtonChecked(hwndDlg, IDC_CHECK_MEDIA),
                            IsDlgButtonChecked(hwndDlg, IDC_CHECK_PATH),
                            GetDlgItem(hwndDlg, IDC_COMBO_PATH)))
                        {
                            PropSheet_SetCurSelByID(GetParent(hwndDlg), IDD_SEARCHDRV);
                        }
                        else
                        {
                            PropSheet_SetCurSelByID(GetParent(hwndDlg), IDD_INSTALLFAILED);
                        }
                    }
                    else
                    {
                        /* FIXME */;
                    }
                    return TRUE;

                default:
                    break;
            }
            break;
        }

        default:
            break;
    }

    return FALSE;
}

static INT_PTR CALLBACK
SearchDrvDlgProc(
    IN HWND hwndDlg,
    IN UINT uMsg,
    IN WPARAM wParam,
    IN LPARAM lParam)
{
    PDEVINSTDATA DevInstData;
    DWORD dwThreadId;

    /* Retrieve pointer to the global setup data */
    DevInstData = (PDEVINSTDATA)GetWindowLongPtr(hwndDlg, GWL_USERDATA);

    switch (uMsg)
    {
        case WM_INITDIALOG:
        {
            HWND hwndControl;
            DWORD dwStyle;

            /* Get pointer to the global setup data */
            DevInstData = (PDEVINSTDATA)((LPPROPSHEETPAGE)lParam)->lParam;
            SetWindowLongPtr(hwndDlg, GWL_USERDATA, (DWORD_PTR)DevInstData);

            DevInstData->hDialog = hwndDlg;
            hwndControl = GetParent(hwndDlg);

            /* Center the wizard window */
            CenterWindow(hwndControl);

            SendDlgItemMessage(
                hwndDlg,
                IDC_DEVICE,
                WM_SETTEXT,
                0,
                (LPARAM)DevInstData->buffer);

            /* Hide the system menu */
            dwStyle = GetWindowLongPtr(hwndControl, GWL_STYLE);
            SetWindowLongPtr(hwndControl, GWL_STYLE, dwStyle & ~WS_SYSMENU);
            break;
        }

        case WM_SEARCH_FINISHED:
        {
            CloseHandle(hThread);
            hThread = 0;
            if (wParam == 0)
                PropSheet_SetCurSelByID(GetParent(hwndDlg), IDD_NODRIVER);
            else
                PropSheet_SetCurSelByID(GetParent(hwndDlg), IDD_INSTALLDRV);
            break;
        }

        case WM_NOTIFY:
        {
            LPNMHDR lpnm = (LPNMHDR)lParam;

            switch (lpnm->code)
            {
                case PSN_SETACTIVE:
                    PropSheet_SetWizButtons(GetParent(hwndDlg), !PSWIZB_NEXT | !PSWIZB_BACK);
                    /* Yes, we can safely ignore the problem (if any) */
                    SetupDiDestroyDriverInfoList(
                        DevInstData->hDevInfo,
                        &DevInstData->devInfoData,
                        SPDIT_COMPATDRIVER);
                    hThread = CreateThread(NULL, 0, FindDriverProc, DevInstData, 0, &dwThreadId);
                    break;

                case PSN_KILLACTIVE:
                    if (hThread != 0)
                    {
                        SetWindowLongPtr(hwndDlg, DWL_MSGRESULT, TRUE);
                        return TRUE;
                    }
                    break;

                case PSN_WIZNEXT:
                    /* Handle a Next button click, if necessary */
                    break;

                default:
                    break;
            }
            break;
        }

        default:
            break;
    }

    return FALSE;
}

static INT_PTR CALLBACK
InstallDrvDlgProc(
    IN HWND hwndDlg,
    IN UINT uMsg,
    IN WPARAM wParam,
    IN LPARAM lParam)
{
    PDEVINSTDATA DevInstData;
    DWORD dwThreadId;

    /* Retrieve pointer to the global setup data */
    DevInstData = (PDEVINSTDATA)GetWindowLongPtr(hwndDlg, GWL_USERDATA);

    switch (uMsg)
    {
        case WM_INITDIALOG:
        {
            HWND hwndControl;
            DWORD dwStyle;

            /* Get pointer to the global setup data */
            DevInstData = (PDEVINSTDATA)((LPPROPSHEETPAGE)lParam)->lParam;
            SetWindowLongPtr(hwndDlg, GWL_USERDATA, (DWORD_PTR)DevInstData);

            DevInstData->hDialog = hwndDlg;
            hwndControl = GetParent(hwndDlg);

            /* Center the wizard window */
            CenterWindow(hwndControl);

            SendDlgItemMessage(
                hwndDlg,
                IDC_DEVICE,
                WM_SETTEXT,
                0,
                (LPARAM)DevInstData->drvInfoData.Description);

            /* Hide the system menu */
            dwStyle = GetWindowLongPtr(hwndControl, GWL_STYLE);
            SetWindowLongPtr(hwndControl, GWL_STYLE, dwStyle & ~WS_SYSMENU);
            break;
        }

        case WM_INSTALL_FINISHED:
        {
            CloseHandle(hThread);
            hThread = 0;
            if (wParam == 0)
            {
                /* Should we reboot? */
                SP_DEVINSTALL_PARAMS installParams;
                installParams.cbSize = sizeof(SP_DEVINSTALL_PARAMS);
                if (SetupDiGetDeviceInstallParams(
                    DevInstData->hDevInfo,
                    &DevInstData->devInfoData,
                    &installParams))
                {
                    if (installParams.Flags & (DI_NEEDRESTART | DI_NEEDREBOOT))
                    {
                        PropSheet_SetCurSelByID(GetParent(hwndDlg), IDD_NEEDREBOOT);
                    }
                    else
                        PropSheet_SetCurSelByID(GetParent(hwndDlg), IDD_FINISHPAGE);
                    break;
                }
            }
            PropSheet_SetCurSelByID(GetParent(hwndDlg), IDD_INSTALLFAILED);
            break;
        }

        case WM_NOTIFY:
        {
            LPNMHDR lpnm = (LPNMHDR)lParam;

            switch (lpnm->code)
            {
                case PSN_SETACTIVE:
                    PropSheet_SetWizButtons(GetParent(hwndDlg), !PSWIZB_NEXT | !PSWIZB_BACK);
                    hThread = CreateThread(NULL, 0, InstallDriverProc, DevInstData, 0, &dwThreadId);
                    break;

                case PSN_KILLACTIVE:
                    if (hThread != 0)
                    {
                        SetWindowLongPtr(hwndDlg, DWL_MSGRESULT, TRUE);
                        return TRUE;
                    }
                    break;

                case PSN_WIZNEXT:
                    /* Handle a Next button click, if necessary */
                    break;

                default:
                    break;
            }
            break;
        }

        default:
            break;
    }

    return FALSE;
}

static INT_PTR CALLBACK
NoDriverDlgProc(
    IN HWND hwndDlg,
    IN UINT uMsg,
    IN WPARAM wParam,
    IN LPARAM lParam)
{
    PDEVINSTDATA DevInstData;
    HWND hwndControl;

    UNREFERENCED_PARAMETER(wParam);

    /* Get pointer to the global setup data */
    DevInstData = (PDEVINSTDATA)GetWindowLongPtr(hwndDlg, GWL_USERDATA);

    switch (uMsg)
    {
        case WM_INITDIALOG:
        {
            BOOL DisableableDevice = FALSE;

            /* Get pointer to the global setup data */
            DevInstData = (PDEVINSTDATA)((LPPROPSHEETPAGE)lParam)->lParam;
            SetWindowLongPtr(hwndDlg, GWL_USERDATA, (DWORD_PTR)DevInstData);

            /* Center the wizard window */
            CenterWindow(GetParent(hwndDlg));

            hwndControl = GetDlgItem(GetParent(hwndDlg), IDCANCEL);
            ShowWindow(hwndControl, SW_HIDE);
            EnableWindow(hwndControl, FALSE);

            /* Set title font */
            SendDlgItemMessage(
                hwndDlg,
                IDC_FINISHTITLE,
                WM_SETFONT,
                (WPARAM)DevInstData->hTitleFont,
                (LPARAM)TRUE);

            /* disable the "do not show this dialog anymore" checkbox
             if the device cannot be disabled */
            CanDisableDevice(
                DevInstData->devInfoData.DevInst,
                NULL,
                &DisableableDevice);
            EnableWindow(
                GetDlgItem(hwndDlg, IDC_DONOTSHOWDLG),
                DisableableDevice);
            break;
        }

        case WM_NOTIFY:
        {
            LPNMHDR lpnm = (LPNMHDR)lParam;

            switch (lpnm->code)
            {
                case PSN_SETACTIVE:
                    /* Enable the correct buttons on for the active page */
                    PropSheet_SetWizButtons(GetParent(hwndDlg), PSWIZB_BACK | PSWIZB_FINISH);
                    break;

                case PSN_WIZBACK:
                    /* Handle a Back button click, if necessary */
                    hwndControl = GetDlgItem(GetParent(hwndDlg), IDCANCEL);
                    ShowWindow(hwndControl, SW_SHOW);
                    EnableWindow(hwndControl, TRUE);
                    PropSheet_SetCurSelByID(GetParent(hwndDlg), IDD_CHSOURCE);
                    return TRUE;

                case PSN_WIZFINISH:
                {
                    BOOL DisableableDevice = FALSE;
                    BOOL IsStarted = FALSE;

                    if (CanDisableDevice(DevInstData->devInfoData.DevInst,
                            NULL,
                            &DisableableDevice) &&
                        DisableableDevice &&
                        IsDeviceStarted(
                            DevInstData->devInfoData.DevInst,
                            NULL,
                            &IsStarted) &&
                        !IsStarted &&
                        SendDlgItemMessage(
                            hwndDlg,
                            IDC_DONOTSHOWDLG,
                            BM_GETCHECK,
                            (WPARAM)0, (LPARAM)0) == BST_CHECKED)
                    {
                        /* disable the device */
                        StartDevice(
                            DevInstData->hDevInfo,
                            &DevInstData->devInfoData,
                            FALSE,
                            0,
                            NULL);
                    }
                    break;
                }

                default:
                    break;
            }
            break;
        }

        default:
            break;
    }

    return FALSE;
}

static INT_PTR CALLBACK
InstallFailedDlgProc(
    IN HWND hwndDlg,
    IN UINT uMsg,
    IN WPARAM wParam,
    IN LPARAM lParam)
{
    PDEVINSTDATA DevInstData;
    UNREFERENCED_PARAMETER(wParam);

    /* Retrieve pointer to the global setup data */
    DevInstData = (PDEVINSTDATA)GetWindowLongPtr(hwndDlg, GWL_USERDATA);

    switch (uMsg)
    {
        case WM_INITDIALOG:
        {
            HWND hwndControl;

            /* Get pointer to the global setup data */
            DevInstData = (PDEVINSTDATA)((LPPROPSHEETPAGE)lParam)->lParam;
            SetWindowLongPtr(hwndDlg, GWL_USERDATA, (DWORD_PTR)DevInstData);

            /* Center the wizard window */
            CenterWindow(GetParent(hwndDlg));

            hwndControl = GetDlgItem(GetParent(hwndDlg), IDCANCEL);
            ShowWindow(hwndControl, SW_HIDE);
            EnableWindow(hwndControl, FALSE);

            SendDlgItemMessage(
                hwndDlg,
                IDC_DEVICE,
                WM_SETTEXT,
                0,
                (LPARAM)DevInstData->drvInfoData.Description);

            /* Set title font */
            SendDlgItemMessage(
                hwndDlg,
                IDC_FINISHTITLE,
                WM_SETFONT,
                (WPARAM)DevInstData->hTitleFont,
                (LPARAM)TRUE);
            break;
        }

        case WM_NOTIFY:
        {
            LPNMHDR lpnm = (LPNMHDR)lParam;

            switch (lpnm->code)
            {
                case PSN_SETACTIVE:
                    /* Enable the correct buttons on for the active page */
                    PropSheet_SetWizButtons(GetParent(hwndDlg), PSWIZB_FINISH);
                    break;

                case PSN_WIZBACK:
                    /* Handle a Back button click, if necessary */
                    break;

                case PSN_WIZFINISH:
                    /* Handle a Finish button click, if necessary */
                    break;

                default:
                    break;
            }
            break;
        }

        default:
            break;
    }

    return FALSE;
}

static INT_PTR CALLBACK
NeedRebootDlgProc(
    IN HWND hwndDlg,
    IN UINT uMsg,
    IN WPARAM wParam,
    IN LPARAM lParam)
{
    PDEVINSTDATA DevInstData;
    UNREFERENCED_PARAMETER(wParam);

    /* Retrieve pointer to the global setup data */
    DevInstData = (PDEVINSTDATA)GetWindowLongPtr(hwndDlg, GWL_USERDATA);

    switch (uMsg)
    {
        case WM_INITDIALOG:
        {
            HWND hwndControl;

            /* Get pointer to the global setup data */
            DevInstData = (PDEVINSTDATA)((LPPROPSHEETPAGE)lParam)->lParam;
            SetWindowLongPtr(hwndDlg, GWL_USERDATA, (DWORD_PTR)DevInstData);

            /* Center the wizard window */
            CenterWindow(GetParent(hwndDlg));

            hwndControl = GetDlgItem(GetParent(hwndDlg), IDCANCEL);
            ShowWindow(hwndControl, SW_HIDE);
            EnableWindow(hwndControl, FALSE);

            SendDlgItemMessage(
                hwndDlg,
                IDC_DEVICE,
                WM_SETTEXT,
                0,
                (LPARAM)DevInstData->drvInfoData.Description);

            /* Set title font */
            SendDlgItemMessage(
                hwndDlg,
                IDC_FINISHTITLE,
                WM_SETFONT,
                (WPARAM)DevInstData->hTitleFont,
                (LPARAM)TRUE);
            break;
        }

        case WM_NOTIFY:
        {
            LPNMHDR lpnm = (LPNMHDR)lParam;

            switch (lpnm->code)
            {
                case PSN_SETACTIVE:
                    /* Enable the correct buttons on for the active page */
                    PropSheet_SetWizButtons(GetParent(hwndDlg), PSWIZB_FINISH);
                    break;

                case PSN_WIZBACK:
                    /* Handle a Back button click, if necessary */
                    break;

                case PSN_WIZFINISH:
                    /* Handle a Finish button click, if necessary */
                    break;

                default:
                    break;
            }
            break;
        }

        default:
            break;
    }

    return FALSE;
}

static INT_PTR CALLBACK
FinishDlgProc(
    IN HWND hwndDlg,
    IN UINT uMsg,
    IN WPARAM wParam,
    IN LPARAM lParam)
{
    PDEVINSTDATA DevInstData;
    UNREFERENCED_PARAMETER(wParam);

    /* Retrieve pointer to the global setup data */
    DevInstData = (PDEVINSTDATA)GetWindowLongPtr(hwndDlg, GWL_USERDATA);

    switch (uMsg)
    {
        case WM_INITDIALOG:
        {
            HWND hwndControl;

            /* Get pointer to the global setup data */
            DevInstData = (PDEVINSTDATA)((LPPROPSHEETPAGE)lParam)->lParam;
            SetWindowLongPtr(hwndDlg, GWL_USERDATA, (DWORD_PTR)DevInstData);

            /* Center the wizard window */
            CenterWindow(GetParent(hwndDlg));

            hwndControl = GetDlgItem(GetParent(hwndDlg), IDCANCEL);
            ShowWindow(hwndControl, SW_HIDE);
            EnableWindow(hwndControl, FALSE);

            SendDlgItemMessage(
                hwndDlg,
                IDC_DEVICE,
                WM_SETTEXT,
                0,
                (LPARAM)DevInstData->drvInfoData.Description);

            /* Set title font */
            SendDlgItemMessage(
                hwndDlg,
                IDC_FINISHTITLE,
                WM_SETFONT,
                (WPARAM)DevInstData->hTitleFont,
                (LPARAM)TRUE);
            break;
        }

        case WM_NOTIFY:
        {
            LPNMHDR lpnm = (LPNMHDR)lParam;

            switch (lpnm->code)
            {
                case PSN_SETACTIVE:
                    /* Enable the correct buttons on for the active page */
                    PropSheet_SetWizButtons(GetParent(hwndDlg), PSWIZB_FINISH);
                    break;

                case PSN_WIZBACK:
                    /* Handle a Back button click, if necessary */
                    break;

                case PSN_WIZFINISH:
                    /* Handle a Finish button click, if necessary */
                    break;

                default:
                    break;
            }
            break;
        }

        default:
            break;
    }

    return FALSE;
}

static HFONT
CreateTitleFont(VOID)
{
    NONCLIENTMETRICSW ncm;
    LOGFONTW LogFont;
    HDC hdc;
    INT FontSize;
    HFONT hFont;

    ncm.cbSize = sizeof(NONCLIENTMETRICSW);
    SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, &ncm, 0);

    LogFont = ncm.lfMessageFont;
    LogFont.lfWeight = FW_BOLD;
    wcscpy(LogFont.lfFaceName, L"MS Shell Dlg");

    hdc = GetDC(NULL);
    FontSize = 12;
    LogFont.lfHeight = 0 - GetDeviceCaps (hdc, LOGPIXELSY) * FontSize / 72;
    hFont = CreateFontIndirectW(&LogFont);
    ReleaseDC(NULL, hdc);

    return hFont;
}

BOOL
DisplayWizard(
    IN PDEVINSTDATA DevInstData,
    IN HWND hwndParent,
    IN UINT startPage)
{
    PROPSHEETHEADER psh;
    HPROPSHEETPAGE ahpsp[IDD_MAXIMUMPAGE + 1];
    PROPSHEETPAGE psp;

    /* zero based index */
    startPage -= IDD_FIRSTPAGE;

    /* Create the Welcome page */
    ZeroMemory(&psp, sizeof(PROPSHEETPAGE));
    psp.dwSize = sizeof(PROPSHEETPAGE);
    psp.dwFlags = PSP_DEFAULT | PSP_HIDEHEADER | PSP_USETITLE;
    psp.hInstance = hDllInstance;
    psp.lParam = (LPARAM)DevInstData;
    psp.pszTitle = MAKEINTRESOURCE(DevInstData->bUpdate ? IDS_UPDATEWIZARDTITLE : IDS_INSTALLWIZARDTITLE);
    psp.pfnDlgProc = WelcomeDlgProc;
    psp.pszTemplate = MAKEINTRESOURCE(IDD_WELCOMEPAGE);
    ahpsp[IDD_WELCOMEPAGE-IDD_FIRSTPAGE] = CreatePropertySheetPage(&psp);

    /* Create the Select Source page */
    psp.dwFlags = PSP_DEFAULT | PSP_USEHEADERTITLE | PSP_USETITLE;
    psp.pszHeaderTitle = MAKEINTRESOURCE(IDS_CHSOURCE_TITLE);
    psp.pfnDlgProc = CHSourceDlgProc;
    psp.pszTemplate = MAKEINTRESOURCE(IDD_CHSOURCE);
    ahpsp[IDD_CHSOURCE-IDD_FIRSTPAGE] = CreatePropertySheetPage(&psp);

    /* Create the Search driver page */
    psp.dwFlags = PSP_DEFAULT | PSP_USEHEADERTITLE | PSP_USETITLE;
    psp.pszHeaderTitle = MAKEINTRESOURCE(IDS_SEARCHDRV_TITLE);
    psp.pfnDlgProc = SearchDrvDlgProc;
    psp.pszTemplate = MAKEINTRESOURCE(IDD_SEARCHDRV);
    ahpsp[IDD_SEARCHDRV-IDD_FIRSTPAGE] = CreatePropertySheetPage(&psp);

    /* Create the Install driver page */
    psp.dwFlags = PSP_DEFAULT | PSP_USEHEADERTITLE | PSP_USETITLE;
    psp.pszHeaderTitle = MAKEINTRESOURCE(IDS_INSTALLDRV_TITLE);
    psp.pfnDlgProc = InstallDrvDlgProc;
    psp.pszTemplate = MAKEINTRESOURCE(IDD_INSTALLDRV);
    ahpsp[IDD_INSTALLDRV-IDD_FIRSTPAGE] = CreatePropertySheetPage(&psp);

    /* Create the No driver page */
    psp.dwFlags = PSP_DEFAULT | PSP_HIDEHEADER | PSP_USETITLE;
    psp.pfnDlgProc = NoDriverDlgProc;
    psp.pszTemplate = MAKEINTRESOURCE(IDD_NODRIVER);
    ahpsp[IDD_NODRIVER-IDD_FIRSTPAGE] = CreatePropertySheetPage(&psp);

    /* Create the Install failed page */
    psp.dwFlags = PSP_DEFAULT | PSP_HIDEHEADER | PSP_USETITLE;
    psp.pfnDlgProc = InstallFailedDlgProc;
    psp.pszTemplate = MAKEINTRESOURCE(IDD_INSTALLFAILED);
    ahpsp[IDD_INSTALLFAILED-IDD_FIRSTPAGE] = CreatePropertySheetPage(&psp);

    /* Create the Need reboot page */
    psp.dwFlags = PSP_DEFAULT | PSP_HIDEHEADER | PSP_USETITLE;
    psp.pfnDlgProc = NeedRebootDlgProc;
    psp.pszTemplate = MAKEINTRESOURCE(IDD_NEEDREBOOT);
    ahpsp[IDD_NEEDREBOOT-IDD_FIRSTPAGE] = CreatePropertySheetPage(&psp);

    /* Create the Finish page */
    psp.dwFlags = PSP_DEFAULT | PSP_HIDEHEADER | PSP_USETITLE;
    psp.pfnDlgProc = FinishDlgProc;
    psp.pszTemplate = MAKEINTRESOURCE(IDD_FINISHPAGE);
    ahpsp[IDD_FINISHPAGE-IDD_FIRSTPAGE] = CreatePropertySheetPage(&psp);

    /* Create the property sheet */
    psh.dwSize = sizeof(PROPSHEETHEADER);
    psh.dwFlags = PSH_WIZARD97 | PSH_WATERMARK | PSH_HEADER;
    psh.hInstance = hDllInstance;
    psh.hwndParent = hwndParent;
    psh.nPages = IDD_MAXIMUMPAGE + 1;
    psh.nStartPage = startPage;
    psh.phpage = ahpsp;
    psh.pszbmWatermark = MAKEINTRESOURCE(IDB_WATERMARK);
    psh.pszbmHeader = MAKEINTRESOURCE(IDB_HEADER);

    /* Create title font */
    DevInstData->hTitleFont = CreateTitleFont();

    /* Display the wizard */
    PropertySheet(&psh);

    DeleteObject(DevInstData->hTitleFont);

    return TRUE;
}
