/*++

Copyright (c) 1995-2000 Microsoft Corporation

Module Name:

    fileq5.c

Abstract:

    Default queue callback function.

Author:

    Ted Miller (tedm) 24-Feb-1995

Revision History:

--*/

#include "precomp.h"
#pragma hdrstop

#define QUEUECONTEXT_SIGNATURE (DWORD)(0x43515053) // 'CQPS'

typedef struct _QUEUECONTEXT {
    DWORD Signature; // an attempt to catch re-use of deleted queuecontext
    HWND OwnerWindow;
    DWORD MainThreadId;
    HWND ProgressDialog;
    HWND ProgressBar;
    BOOL Cancelled;
    PTSTR CurrentSourceName;
    BOOL ScreenReader;
    BOOL MessageBoxUp;
    WPARAM  PendingUiType;
    PVOID   PendingUiParameters;
    UINT    CancelReturnCode;
    BOOL DialogKilled;
    //
    // If the SetupInitDefaultQueueCallbackEx is used, the caller can
    // specify an alternate handler for progress. This is useful to
    // get the default behavior for disk prompting, error handling, etc,
    // but to provide a gas gauge embedded, say, in a wizard page.
    //
    // The alternate window is sent ProgressMsg once when the copy queue
    // is started (wParam = 0. lParam = number of files to copy).
    // It is then also sent once per file copied (wParam = 1. lParam = 0).
    //
    // NOTE: a silent installation (i.e., no progress UI) can be accomplished
    // by specifying an AlternateProgressWindow handle of INVALID_HANDLE_VALUE.
    //
    HWND AlternateProgressWindow;
    UINT ProgressMsg;
    UINT NoToAllMask;

    HANDLE UiThreadHandle;
    //
    // instead of posting responses to main thread, use an event with flags
    //
    HANDLE hEvent;
    BOOL bDialogExited;
    LPARAM lParam;

#ifdef NOCANCEL_SUPPORT
    BOOL AllowCancel;
#endif

} QUEUECONTEXT, *PQUEUECONTEXT;

typedef struct _VERDLGCONTEXT {
    PQUEUECONTEXT QueueContext;
    UINT Notification;
    UINT_PTR Param1;
    UINT_PTR Param2;
} VERDLGCONTEXT, *PVERDLGCONTEXT;

#define WMX_PROGRESSTHREAD  (WM_APP+0)
#define WMX_KILLDIALOG      (WM_APP+1)
#define WMX_HELLO           (WM_APP+2)
#define WMX_PERFORMUI       (WM_APP+3)

#define UI_NONE             0
#define UI_COPYERROR        1
#define UI_DELETEERROR      2
#define UI_RENAMEERROR      3
#define UI_NEEDMEDIA        4
#define UI_MISMATCHERROR    5
#define UI_BACKUPERROR      6


typedef struct _COPYERRORUI {
    TCHAR       Buffer[MAX_PATH];
    PTCHAR      Filename;
    PFILEPATHS  FilePaths;
    DWORD       Flags;
    PTSTR       PathOut;
} COPYERRORUI, *PCOPYERRORUI;

typedef struct _NEEDMEDIAUI {
    PSOURCE_MEDIA   SourceMedia;
    DWORD           Flags;
    PTSTR           PathOut;
} NEEDMEDIAUI, *PNEEDMEDIAUI;


PCTSTR DialogPropName = TEXT("_context");

INT_PTR
pSetupProgressDlgProc(
    IN HWND   hdlg,
    IN UINT   msg,
    IN WPARAM wParam,
    IN LPARAM lParam
    );

LPARAM
pPerformUi (
    IN  PQUEUECONTEXT   Context,
    IN  UINT            UiType,
    IN  PVOID           UiParameters
    );


VOID
__cdecl
pSetupProgressThread(
    IN PVOID Context
    )

/*++

Routine Description:

    Thread entry point for setup file progress indicator.
    Puts up a dialog box.

Arguments:

    Context - supplies queue context.

Return Value:

    0 if unsuccessful, non-0 if successful.

--*/

{
    PQUEUECONTEXT context;
    INT_PTR i;
    MSG msg;

    //
    // Force this thread to have a message queue, just in case.
    //
    PeekMessage(&msg,NULL,0,0,PM_NOREMOVE);

    //
    // The thread parameter is the queue context.
    //
    context = Context;

    //
    // Create the progress dialog box.
    //
    i = DialogBoxParam(
            MyDllModuleHandle,
            MAKEINTRESOURCE(IDD_FILEPROGRESS),
            context->OwnerWindow,
            pSetupProgressDlgProc,
            (LPARAM)context
            );

    //
    // flag that this is the very last time hEvent will be set
    //
    context->bDialogExited = TRUE;
    SetEvent(context->hEvent);

    //
    // Done.
    //
    _endthread();
}

BOOL
pWaitForUiResponse(
    IN OUT PQUEUECONTEXT Context
    )
/*++

Routine Description:

    Waits for UI event to be set

Arguments:

    Context - supplies queue-context structure

Return Value:

    FALSE = failure

--*/
{
    BOOL KeepWaiting = TRUE;
    DWORD WaitProcStatus;

    if (Context->hEvent == NULL) {
        MYASSERT(Context->hEvent);
        return FALSE;
    }
    if (Context->bDialogExited) {
        //
        // dialog has already exited, we wont get another event
        //
        return FALSE;
    }

    while (KeepWaiting) {
        WaitProcStatus = MyMsgWaitForMultipleObjectsEx(
            1,
            &Context->hEvent,
            INFINITE,
            QS_ALLINPUT,
            MWMO_ALERTABLE | MWMO_INPUTAVAILABLE);
        switch (WaitProcStatus) {
        case WAIT_OBJECT_0 + 1: { // Process gui messages
            MSG msg;

            while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }

            // fall through ...
        }
        case WAIT_IO_COMPLETION:
            break;

        case WAIT_OBJECT_0:
        case WAIT_TIMEOUT:
        default:
            KeepWaiting = FALSE;
            break;
        }
    }

    return TRUE;
}

UINT
PostUiMessage (
    IN      PQUEUECONTEXT Context,
    IN      UINT          UiType,
    IN      UINT          CancelCode,
    IN OUT  PVOID         UiParameters
    )
{
    MSG msg;

    if(IsWindow(Context->ProgressDialog)) {
        //
        // Let progress ui thread handle it.
        //
        Context->lParam = FILEOP_ABORT; // in case nobody gets chance to set Context->lParam
        PostMessage(
            Context->ProgressDialog,
            WMX_PERFORMUI,
            MAKEWPARAM(UiType,CancelCode),
            (LPARAM)UiParameters
            );
        pWaitForUiResponse(Context);
        return (UINT)Context->lParam;
    } else {
        //
        // There is no progress thread so do it synchronously.
        //
        return (UINT)pPerformUi(Context,UiType,UiParameters);
    }

    return 0;
}


UINT
pNotificationStartQueue(
    IN PQUEUECONTEXT Context
    )

/*++

Routine Description:

    Handle SPFILENOTIFY_STARTQUEUE.

    Creates a progress dialog in a separate thread.

Arguments:

    Context - supplies queue context.

Return Value:

    0 if unsuccessful, non-0 if successful.

--*/

{
    ULONG_PTR Thread;
    MSG msg;

    //
    // SetupCommitFileQueue could have been called in a different
    // thread. Adjust the thread id.
    //
    Context->MainThreadId = GetCurrentThreadId();

    //
    // Force this thread to have a message queue. If we don't do this,
    // then PostMessage, PostThreadMessage, etc can fail, which results
    // in hangs in some cases since we rely heavily on these for
    // progress, synchronization, etc.
    //
    PeekMessage(&msg,NULL,0,0,PM_NOREMOVE);

    if(Context->AlternateProgressWindow) {
        //
        // Either the caller is supplying their own window for progress UI,
        // or this is a silent install (AlternateProgressWindow is
        // INVALID_HANDLE_VALUE).
        //
        return(TRUE);
    } else {
        //
        // Fire up the progress dialog in a separate thread.
        // This allows it to be responsive without suspending
        // the file operations.
        //
        Thread = _beginthread(
                    pSetupProgressThread,
                    0,
                    Context
                    );

        if(Thread == -1) {
            //
            // assume OOM
            //
            SetLastError(ERROR_NOT_ENOUGH_MEMORY);
            return(0);
        }

        //
        // Wait for notification from the thread about the state
        // of the dialog. Assume out of memory if we fail
        //
        if(!pWaitForUiResponse(Context) || Context->bDialogExited) {
            SetLastError(ERROR_NOT_ENOUGH_MEMORY);
            return FALSE;
        } else {
            return TRUE;
        }
    }
}


UINT
pNotificationStartEndSubqueue(
    IN PQUEUECONTEXT Context,
    IN BOOL          Start,
    IN UINT_PTR      Operation,
    IN UINT_PTR      OpCount
    )

/*++

Routine Description:

    Handle SPFILENOTIFY_STARTSUBQUEUE, SPFILENOTIFY_ENDSUBQUEUE.

    Initializes/terminates a progress control.
    Also sets progress dialog caption.

Arguments:

    Context - supplies queue context.

    Start - if TRUE, then this routine is being called to handle
        a subqueue start notification. Otherwise it's supposed to
        handle a subqueue end notification.

    Operation - one of FILEOP_COPY, FILEOP_DELETE, FILEOP_RENAME.

    OpCount - supplies number of copies, renames, or deletes.

Return Value:

    0 if unsuccessful, non-0 if successful.

--*/

{
    UINT rc;
    UINT CaptionStringId;
    TCHAR ParentText[256];
    BOOL GotParentText;
    PCTSTR CaptionText;
    UINT AnimationId;
    HWND Animation;

    rc = 1;         // assume success.

    if(Context->Cancelled) {
        SetLastError(ERROR_CANCELLED);
        return(0);
    }

    if(Start) {

        if(IsWindow(Context->OwnerWindow)
        && GetWindowText(Context->OwnerWindow,ParentText,256)) {
            GotParentText = TRUE;
        } else {
            GotParentText = FALSE;
        }

        //
        // Clean out the text fields first.
        //
        if(IsWindow(Context->ProgressDialog)) {
            SetDlgItemText(Context->ProgressDialog,IDT_TEXT1,TEXT(""));
            SetDlgItemText(Context->ProgressDialog,IDT_TEXT2,TEXT(""));
        }

        switch(Operation) {

        case FILEOP_COPY:
            //
            // IDT_TEXT1 = target name sans path
            // IDT_TEXT2 = target name with path
            //
            if(IsWindow(Context->ProgressDialog)) {
                ShowWindow(GetDlgItem(Context->ProgressDialog,IDT_TEXT2),SW_SHOW);
            }
            CaptionStringId = GotParentText ? IDS_COPY_CAPTION1 : IDS_COPY_CAPTION2;
            AnimationId = IDA_FILECOPY;
            break;

        case FILEOP_RENAME:
            if(IsWindow(Context->ProgressDialog)) {
                ShowWindow(GetDlgItem(Context->ProgressDialog,IDT_TEXT2),SW_SHOW);
            }
            CaptionStringId = GotParentText ? IDS_RENAME_CAPTION1 : IDS_RENAME_CAPTION2;
            AnimationId = IDA_FILECOPY;
            break;

        case FILEOP_DELETE:
            //
            // IDT_TEXT1 = target name sans path
            // IDT_TEXT2 = path
            //
            if(IsWindow(Context->ProgressDialog)) {
                ShowWindow(GetDlgItem(Context->ProgressDialog,IDT_TEXT2),SW_HIDE);
            }
            CaptionStringId = GotParentText ? IDS_DELETE_CAPTION1 : IDS_DELETE_CAPTION2;
            AnimationId = IDA_FILEDEL;
            break;

        case FILEOP_BACKUP:
            // handle the new backup case (this is for a backup queue as opposed to on demand
            if(IsWindow(Context->ProgressDialog)) {
                ShowWindow(GetDlgItem(Context->ProgressDialog,IDT_TEXT2),SW_SHOW);
            }
            CaptionStringId = GotParentText ? IDS_BACKUP_CAPTION1 : IDS_BACKUP_CAPTION2;
            AnimationId = IDA_FILECOPY;
            break;

        default:
            SetLastError(ERROR_INVALID_PARAMETER);
            rc = 0;
            break;
        }

        if(rc) {
            //
            // Set dialog caption.
            //
            if(GotParentText) {
                CaptionText = FormatStringMessage(CaptionStringId,ParentText);
            } else {
                CaptionText = MyLoadString(CaptionStringId);
            }
            if(CaptionText) {
                if(IsWindow(Context->ProgressDialog)) {
                    if(!SetWindowText(Context->ProgressDialog,CaptionText)) {
                        SetWindowText(Context->ProgressDialog,TEXT(""));
                    }
                }
                MyFree(CaptionText);
            }

            if(Context->AlternateProgressWindow) {
                //
                // If this is really an alternate progress window, notify it
                // about the number of operations. Copy only.
                //
                if((Operation == FILEOP_COPY) &&
                   (Context->AlternateProgressWindow != INVALID_HANDLE_VALUE)) {

                    SendMessage(Context->AlternateProgressWindow,
                                Context->ProgressMsg,
                                0,
                                OpCount
                               );
                }
            } else {
                //
                // Set up the progress control. Each file will be 1 tick.
                //
                if(IsWindow(Context->ProgressBar)) {
                    SendMessage(Context->ProgressBar,PBM_SETRANGE,0,MAKELPARAM(0,OpCount));
                    SendMessage(Context->ProgressBar,PBM_SETSTEP,1,0);
                    SendMessage(Context->ProgressBar,PBM_SETPOS,0,0);
                }

                //
                // And set up the animation control based on the operation type.
                //
                if(OpCount && IsWindow(Context->ProgressDialog)) {

                    Animation = GetDlgItem(Context->ProgressDialog,IDA_ANIMATION);

                    if(Animation) {
                        Animate_Open(Animation,MAKEINTRESOURCE(AnimationId));
                        Animate_Play(Animation,0,-1,-1);
                    }
                }
            }
        }
    } else {
        //
        // Stop the animation control. Note that if the op count was 0
        // then we never started it, so stopping/unloading will give an error,
        // which we ignore. It's not harmful.
        //
        if(!Context->AlternateProgressWindow && IsWindow(Context->ProgressDialog)) {

            Animation = GetDlgItem(Context->ProgressDialog,IDA_ANIMATION);
            if (Animation) {
                Animate_Stop(Animation);
                Animate_Close(Animation);
            }
        }
    }

    return(rc);
}


UINT
pNotificationStartOperation(
    IN PQUEUECONTEXT Context,
    IN PFILEPATHS    FilePaths,
    IN UINT_PTR      Operation
    )

/*++

Routine Description:

    Handle SPFILENOTIFY_STARTRENAME, SPFILENOTIFY_STARTDELETE,
    SPFILENOTIFY_STARTCOPY or SPFILENOTIFY_STARTBACKUP.

    Updates text in the progress dialog to indicate the files
    involved in the operation.

Arguments:

    Context - supplies queue context.

    Start - if TRUE, then this routine is being called to handle
        a subqueue start notification. Otherwise it's supposed to
        handle a subqueue end notification.

    Operation - one of FILEOP_COPY, FILEOP_DELETE, FILEOP_RENAME.

    OpCount - supplies number of copies, renames, or deletes.

Return Value:

    FILEOP_ABORT if error, otherwise FILEOP_DOIT.
--*/

{
    PCTSTR Text1,Text2;
    PTSTR p;
    TCHAR Target[MAX_PATH];
    UINT rc;
    DWORD ec;

    if(Context->Cancelled) {
        SetLastError(ERROR_CANCELLED);
        return(FILEOP_ABORT);
    }

    Text1 = Text2 = NULL;
    rc = FILEOP_ABORT;
    ec = ERROR_NOT_ENOUGH_MEMORY;

    switch(Operation) {
    case FILEOP_COPY:
        lstrcpyn(Target,FilePaths->Target,MAX_PATH);
        if(p = _tcsrchr(Target,TEXT('\\'))) {
            //
            // Ignore the source filename completely.
            //
            *p++ = 0;
            Text1 = DuplicateString(p);
            Text2 = FormatStringMessage(IDS_FILEOP_TO,Target);
        } else {
            //
            // Assume not full path -- strange case, but deal with it anyway.
            //
            Text1 = DuplicateString(FilePaths->Target);
            Text2 = DuplicateString(TEXT(""));
        }
        break;

    case FILEOP_RENAME:

        Text1 = DuplicateString(FilePaths->Source);
        if(p = _tcsrchr(FilePaths->Target,TEXT('\\'))) {
            p++;
        } else {
            p = (PTSTR)FilePaths->Target;
        }
        Text2 = FormatStringMessage(IDS_FILEOP_TO,p);
        break;

    case FILEOP_DELETE:

        lstrcpyn(Target,FilePaths->Target,MAX_PATH);
        if(p = _tcsrchr(Target,TEXT('\\'))) {
            *p++ = 0;
            Text1 = DuplicateString(p);
            Text2 = FormatStringMessage(IDS_FILEOP_FROM,Target);
        } else {
            //
            // Assume not full path -- strange case, but deal with it anyway.
            //
            Text1 = DuplicateString(FilePaths->Target);
            Text2 = DuplicateString(TEXT(""));
        }
        break;

    case FILEOP_BACKUP:
        lstrcpyn(Target,FilePaths->Source,MAX_PATH);
        if(p = _tcsrchr(Target,TEXT('\\'))) {
            //
            // FilePaths->Source = what we're backing up (which is target of a restore)
            // FilePaths->Target = where we're backing up to (block backup) or NULL (demand backup)
            //
            *p++ = 0;
            if (FilePaths->Target == NULL) {
                // Backing up <Filename>
                Text1 = FormatStringMessage(IDS_FILEOP_BACKUP,p);
            } else {
                // <Filename> (title already says Backing up)
                Text1 = DuplicateString(p);
            }
            // From <Directory>
            Text2 = FormatStringMessage(IDS_FILEOP_FROM,Target);
        } else {
            //
            // Assume not full path -- strange case, but deal with it anyway.
            //
            if (FilePaths->Source == NULL) {
                Text1 = FormatStringMessage(IDS_FILEOP_BACKUP,Target);
            } else {
                Text1 = DuplicateString(Target);
            }
            Text2 = DuplicateString(TEXT(""));
        }
        break;


    default:
        ec = ERROR_INVALID_PARAMETER;
        break;
    }

    if(Text1 && Text2) {
        if(IsWindow(Context->ProgressDialog)) {
            SetDlgItemText(Context->ProgressDialog,IDT_TEXT1,Text1);
            SetDlgItemText(Context->ProgressDialog,IDT_TEXT2,Text2);
        }
        rc = FILEOP_DOIT;
    }

    if(Text1) {
        MyFree(Text1);
    }
    if(Text2) {
        MyFree(Text2);
    }
    SetLastError(ec);
    return(rc);
}


UINT
pNotificationErrorCopy(
    IN  PQUEUECONTEXT Context,
    IN  PFILEPATHS    FilePaths,
    OUT PTSTR         PathOut
    )
{
    UINT rc;
    COPYERRORUI CopyError;


    CopyError.FilePaths = FilePaths;
    CopyError.PathOut = PathOut;

    //
    // Buffer gets the pathname part of the source
    // and p points to the filename part of the source.
    //
    lstrcpyn(CopyError.Buffer,FilePaths->Source,MAX_PATH);
    CopyError.Filename = _tcsrchr(CopyError.Buffer,TEXT('\\'));
    *CopyError.Filename++ = 0;

    //
    // The noskip and warnifskip flags are really mutually exclusive
    // but we don't try to enforce that here. Just pass through as
    // appropriate.
    //
    CopyError.Flags = 0;
    if(FilePaths->Flags & SP_COPY_NOSKIP) {
        CopyError.Flags |= IDF_NOSKIP;
    }
    if(FilePaths->Flags & SP_COPY_WARNIFSKIP) {
        CopyError.Flags |= IDF_WARNIFSKIP;
    }
    //
    // Also pass through the 'no browse' flag.
    //
    if(FilePaths->Flags & SP_COPY_NOBROWSE) {
        CopyError.Flags |= IDF_NOBROWSE;
    }

    rc = PostUiMessage (Context, UI_COPYERROR, DPROMPT_CANCEL, &CopyError);

    switch(rc) {

    case DPROMPT_SUCCESS:
        //
        // If a new path is indicated, verify that it actually changed.
        //
        if(CopyError.PathOut[0] &&
            !lstrcmpi(CopyError.Buffer,CopyError.PathOut)) {
            CopyError.PathOut[0] = 0;
        }
        rc = FILEOP_RETRY;
        break;

    case DPROMPT_SKIPFILE:
        rc = FILEOP_SKIP;
        break;

    case DPROMPT_CANCEL:
        SetLastError(ERROR_CANCELLED);
        rc = FILEOP_ABORT;
        break;

    default:
        SetLastError(ERROR_NOT_ENOUGH_MEMORY);
        rc = FILEOP_ABORT;
        break;
    }

    return(rc);
}

UINT
pNotificationStartRegistration(
    IN  PQUEUECONTEXT Context,
    IN  PSP_REGISTER_CONTROL_STATUS ControlStatus,
    IN  BOOL Register
    )
{

    return FILEOP_DOIT;
}


UINT
pNotificationErrorDelete(
    IN  PQUEUECONTEXT Context,
    IN  PFILEPATHS    FilePaths
    )
{
    UINT rc;

    //
    // Certain errors are not actually errors.
    //
    if((FilePaths->Win32Error == ERROR_FILE_NOT_FOUND)
    || (FilePaths->Win32Error == ERROR_PATH_NOT_FOUND)) {
        return(FILEOP_SKIP);
    }

    rc = PostUiMessage (Context, UI_DELETEERROR, DPROMPT_CANCEL, FilePaths);

    switch(rc) {

    case DPROMPT_SUCCESS:
        return(FILEOP_RETRY);

    case DPROMPT_SKIPFILE:
        return(FILEOP_SKIP);

    case DPROMPT_CANCEL:
        SetLastError(ERROR_CANCELLED);
        return(FILEOP_ABORT);

    default:
        SetLastError(ERROR_NOT_ENOUGH_MEMORY);
        return(FILEOP_ABORT);
    }
}


UINT
pNotificationErrorRename(
    IN  PQUEUECONTEXT Context,
    IN  PFILEPATHS    FilePaths
    )
{
    UINT rc;

    rc = PostUiMessage (Context, UI_RENAMEERROR, DPROMPT_CANCEL, FilePaths);

    switch(rc) {

    case DPROMPT_SUCCESS:
        return(FILEOP_RETRY);

    case DPROMPT_SKIPFILE:
        return(FILEOP_SKIP);

    case DPROMPT_CANCEL:
        SetLastError(ERROR_CANCELLED);
        return(FILEOP_ABORT);

    default:
        SetLastError(ERROR_NOT_ENOUGH_MEMORY);
        return(FILEOP_ABORT);
    }
}

UINT
pNotificationErrorBackup(
    IN  PQUEUECONTEXT Context,
    IN  PFILEPATHS    FilePaths
    )
{
    UINT rc;

    if(!(FilePaths->Flags & SP_BACKUP_SPECIAL)) {
        return FILEOP_SKIP;
    }

    rc = PostUiMessage (Context, UI_BACKUPERROR, DPROMPT_CANCEL, FilePaths);

    switch(rc) {

    case DPROMPT_SUCCESS:
        return(FILEOP_RETRY);

    case DPROMPT_SKIPFILE:
        return(FILEOP_SKIP);

    case DPROMPT_CANCEL:
        SetLastError(ERROR_CANCELLED);
        return(FILEOP_ABORT);

    default:
        SetLastError(ERROR_NOT_ENOUGH_MEMORY);
        return(FILEOP_ABORT);
    }
}


UINT
pNotificationNeedMedia(
    IN  PQUEUECONTEXT Context,
    IN  PSOURCE_MEDIA SourceMedia,
    OUT PTSTR         PathOut
    )
{
    UINT rc;
    TCHAR Buffer[MAX_PATH];
    NEEDMEDIAUI NeedMedia;

    if(Context->Cancelled) {
        SetLastError(ERROR_CANCELLED);
        return(FILEOP_ABORT);
    }

    NeedMedia.SourceMedia = SourceMedia;
    NeedMedia.PathOut = PathOut;

    //
    // Remember the name of this media.
    //
    if(Context->CurrentSourceName) {
        MyFree(Context->CurrentSourceName);
        Context->CurrentSourceName = NULL;
    }
    if(SourceMedia->Description) {
        Context->CurrentSourceName = DuplicateString(SourceMedia->Description);
        if(!Context->CurrentSourceName) {
            SetLastError(ERROR_NOT_ENOUGH_MEMORY);
            return(FILEOP_ABORT);
        }
    }

    //
    // Set the source file in the progress dialog
    // so it matches the file being sought.
    //
    if(!(SourceMedia->Flags & SP_FLAG_CABINETCONTINUATION)) {
        if(IsWindow(Context->ProgressDialog) && !Context->ScreenReader) {
            DWORD chars;
            lstrcpyn(Buffer,SourceMedia->SourcePath,MAX_PATH);
            pSetupConcatenatePaths(Buffer,SourceMedia->SourceFile,MAX_PATH,NULL);
            SetTruncatedDlgItemText(Context->ProgressDialog,IDT_TEXT1,Buffer);
            SetDlgItemText(Context->ProgressDialog,IDT_TEXT2,TEXT(""));
        }
    }

    //
    // The noskip and warnifskip flags are really mutually exclusive
    // but we don't try to enforce that here. Just pass through as
    // appropriate.
    //
    // Allow skip if this is not a cabinet continuation and
    // the noskip flag is not set.
    //
    NeedMedia.Flags = IDF_CHECKFIRST;
    if(SourceMedia->Flags & (SP_FLAG_CABINETCONTINUATION | SP_COPY_NOSKIP)) {
        NeedMedia.Flags |= IDF_NOSKIP;
    }
    if(SourceMedia->Flags & SP_COPY_WARNIFSKIP) {
        NeedMedia.Flags |= IDF_WARNIFSKIP;
    }
    if(SourceMedia->Flags & SP_COPY_NOBROWSE) {
        NeedMedia.Flags |= IDF_NOBROWSE;
    }

    rc = PostUiMessage (Context, UI_NEEDMEDIA, DPROMPT_CANCEL, &NeedMedia);

    switch(rc) {

    case DPROMPT_SUCCESS:
        //
        // If the path really has changed, then return NEWPATH.
        // Otherwise return DOIT. Account for trailing backslash
        // differences.
        //
        lstrcpyn(Buffer,SourceMedia->SourcePath,MAX_PATH);

        rc = lstrlen(Buffer);
        if(rc && (*CharPrev(Buffer,Buffer+rc) == TEXT('\\'))) {
            Buffer[rc-1] = TEXT('\0'); // valid to do if last char is '\'
        }

        rc = lstrlen(NeedMedia.PathOut);
        if(rc && (*CharPrev(NeedMedia.PathOut,NeedMedia.PathOut+rc) == TEXT('\\'))) {
            NeedMedia.PathOut[rc-1] = TEXT('\0'); // valid to do if last char is '\'
        }

        rc = (lstrcmpi(SourceMedia->SourcePath,NeedMedia.PathOut) ?
            FILEOP_NEWPATH : FILEOP_DOIT);

        //
        // Make sure <drive>: ends with a \.
        //
        if(NeedMedia.PathOut[0] && (NeedMedia.PathOut[1] == TEXT(':')) &&
            !NeedMedia.PathOut[2]) {
            NeedMedia.PathOut[2] = TEXT('\\');
            NeedMedia.PathOut[3] = TEXT('\0');
        }

        break;

    case DPROMPT_SKIPFILE:
        rc = FILEOP_SKIP;
        break;

    case DPROMPT_CANCEL:
        SetLastError(ERROR_CANCELLED);
        rc = FILEOP_ABORT;
        break;

    default:
        SetLastError(ERROR_NOT_ENOUGH_MEMORY);
        rc = FILEOP_ABORT;
        break;
    }

    return(rc);
}


INT_PTR
pNotificationVersionDlgProc(
    IN HWND   hdlg,
    IN UINT   msg,
    IN WPARAM wParam,
    IN LPARAM lParam
    )
{
    PVERDLGCONTEXT context;
    PFILEPATHS filePaths;
    HWND hwnd;
    TCHAR text[128];
    TCHAR Buffer[MAX_PATH*2];
    PCTSTR message;
    int i;
    TCHAR SourceLangName[128];
    TCHAR TargetLangName[128];

    switch(msg) {

    case WM_INITDIALOG:

        context = (PVERDLGCONTEXT)lParam;
        MYASSERT(context != NULL);

        filePaths = (PFILEPATHS)context->Param1;

        SetProp(hdlg,DialogPropName,(HANDLE)context);

        //
        // Set the source and target filenames. Hack: if the source filename
        // looks like one of our temporary filenames from a cabinet extraction,
        // hide it.
        //
        message = pSetupGetFileTitle(filePaths->Source);
        i = lstrlen(message);
        if((i > 8)
        && ((TCHAR)CharUpper((LPTSTR)message[0]) == TEXT('S'))
        && ((TCHAR)CharUpper((LPTSTR)message[1]) == TEXT('E'))
        && ((TCHAR)CharUpper((LPTSTR)message[2]) == TEXT('T'))
        && ((TCHAR)CharUpper((LPTSTR)message[3]) == TEXT('P'))
        && !lstrcmpi(message+(i-4),TEXT(".TMP"))) {

            ShowWindow(GetDlgItem(hdlg,IDT_TEXT7),SW_HIDE);

        } else {
            GetDlgItemText(hdlg,IDT_TEXT7,text,SIZECHARS(text));
            message = FormatStringMessageFromString(text,filePaths->Source);
            if (message == NULL) goto no_memory;
            SetTruncatedDlgItemText(hdlg,IDT_TEXT7,message);
            MyFree(message);
        }

        GetDlgItemText(hdlg,IDT_TEXT8,text,SIZECHARS(text));
        message = FormatStringMessageFromString(text,filePaths->Target);
        if (message == NULL) goto no_memory;
        SetTruncatedDlgItemText(hdlg,IDT_TEXT8,message);
        MyFree(message);

        if (context->Notification & SPFILENOTIFY_LANGMISMATCH) {
            //
            // Language mismatch has the highest priority.
            //
            context->Notification = SPFILENOTIFY_LANGMISMATCH; // force other bits off, for NoToAll

            //
            // Format the overwrite question.
            //
            if(PRIMARYLANGID(LOWORD(context->Param2))==LANG_NEUTRAL) {
                LoadString(
                    MyDllModuleHandle,
                    IDS_LANG_NEUTRAL,
                    SourceLangName,
                    SIZECHARS(SourceLangName)
                    );
            } else {
                i = GetLocaleInfo(
                        MAKELCID(LOWORD(context->Param2),SORT_DEFAULT),
                        LOCALE_SLANGUAGE,
                        SourceLangName,
                        SIZECHARS(SourceLangName)
                        );
                if(!i) {
                    LoadString(
                        MyDllModuleHandle,
                        IDS_LANG_UNKNOWN,
                        SourceLangName,
                        SIZECHARS(SourceLangName)
                        );
                }
            }


            if(PRIMARYLANGID(HIWORD(context->Param2))==LANG_NEUTRAL) {
                LoadString(
                    MyDllModuleHandle,
                    IDS_LANG_NEUTRAL,
                    TargetLangName,
                    SIZECHARS(TargetLangName)
                    );
            } else {
                i = GetLocaleInfo(
                        MAKELCID(HIWORD(context->Param2),SORT_DEFAULT),
                        LOCALE_SLANGUAGE,
                        TargetLangName,
                        SIZECHARS(TargetLangName)
                        );
                if(!i) {
                    LoadString(
                        MyDllModuleHandle,
                        IDS_LANG_UNKNOWN,
                        TargetLangName,
                        SIZECHARS(TargetLangName)
                        );
                }
            }
            GetDlgItemText(hdlg,IDT_TEXT4,text,SIZECHARS(text));
            message = FormatStringMessageFromString(text,TargetLangName,SourceLangName);
            if (message == NULL) goto no_memory;
            SetDlgItemText(hdlg,IDT_TEXT4,message);
            MyFree(message);

            //
            // Turn off the TARGETNEWER and TARGETEXISTS messages.
            //
            hwnd = GetDlgItem(hdlg,IDT_TEXT2);
            ShowWindow(hwnd,SW_HIDE);
            hwnd = GetDlgItem(hdlg,IDT_TEXT3);
            ShowWindow(hwnd,SW_HIDE);
            hwnd = GetDlgItem(hdlg,IDT_TEXT5);
            ShowWindow(hwnd,SW_HIDE);
            hwnd = GetDlgItem(hdlg,IDT_TEXT6);
            ShowWindow(hwnd,SW_HIDE);

        } else if (context->Notification & SPFILENOTIFY_TARGETNEWER) {
            //
            // Target being newer has second highest priority.
            //
            context->Notification = SPFILENOTIFY_TARGETNEWER; // force other bits off, for NoToAll

            //
            // Turn off the LANGMISMATCH and TARGETEXISTS messages.
            //
            hwnd = GetDlgItem(hdlg,IDT_TEXT1);
            ShowWindow(hwnd,SW_HIDE);
            hwnd = GetDlgItem(hdlg,IDT_TEXT3);
            ShowWindow(hwnd,SW_HIDE);
            hwnd = GetDlgItem(hdlg,IDT_TEXT4);
            ShowWindow(hwnd,SW_HIDE);
            hwnd = GetDlgItem(hdlg,IDT_TEXT6);
            ShowWindow(hwnd,SW_HIDE);

        } else {            // must be exactly SPFILENOTIFY_TARGETEXISTS
            //
            // Target existing has the lowest priority.
            //
            // Turn off the LANGMISMATCH and TARGETNEWER messages.
            //
            hwnd = GetDlgItem(hdlg,IDT_TEXT1);
            ShowWindow(hwnd,SW_HIDE);
            hwnd = GetDlgItem(hdlg,IDT_TEXT2);
            ShowWindow(hwnd,SW_HIDE);
            hwnd = GetDlgItem(hdlg,IDT_TEXT4);
            ShowWindow(hwnd,SW_HIDE);
            hwnd = GetDlgItem(hdlg,IDT_TEXT5);
            ShowWindow(hwnd,SW_HIDE);

        }

        PostMessage(hdlg,WMX_HELLO,0,0);
        break;

    case WMX_HELLO:
        //
        // If this guy has no owner force him to the foreground.
        // This catches cases where people are using a series of
        // dialogs and then some setup apis, because when they
        // close a dialog focus switches away from them.
        //
        hwnd = GetWindow(hdlg,GW_OWNER);
        if(!IsWindow(hwnd)) {
            SetForegroundWindow(hdlg);
        }
        break;

    case WM_COMMAND:
        context = (PVERDLGCONTEXT)GetProp(hdlg,DialogPropName);
        MYASSERT(context != NULL);
        switch (GET_WM_COMMAND_ID(wParam,lParam)) {

        case IDYES:
            EndDialog(hdlg,IDYES);  // copy this file
            break;

        case IDNO:
            EndDialog(hdlg,IDNO);   // skip this file
            break;

        case IDB_NOTOALL:
            //
            // No to All was selected.  Add this notification type to the
            // NoToAllMask so that we don't ask about it again.
            //
            context->QueueContext->NoToAllMask |= context->Notification;
            EndDialog(hdlg,IDNO);   // skip this file
            break;
        }
        break;

    default:
        return FALSE;
    }
    return TRUE;

no_memory:
    pSetupOutOfMemory(
        IsWindow(context->QueueContext->ProgressDialog) ?
            context->QueueContext->ProgressDialog : context->QueueContext->OwnerWindow
        );
    EndDialog(hdlg,IDNO);   // skip this file
    return TRUE;
}


LPARAM
pPerformUi (
    IN  PQUEUECONTEXT   Context,
    IN  UINT            UiType,
    IN  PVOID           UiParameters
    )
{
    PCOPYERRORUI    CopyError;
    PFILEPATHS      FilePaths;
    PNEEDMEDIAUI    NeedMedia;
    INT_PTR         rc;
    HWND            Animation;

    if(!Context->AlternateProgressWindow && IsWindow(Context->ProgressDialog)) {
        Animation = GetDlgItem(Context->ProgressDialog,IDA_ANIMATION);
    } else {
        Animation = NULL;
    }

    switch (UiType) {

    case UI_COPYERROR:
        CopyError = (PCOPYERRORUI)UiParameters;
        if (Animation) {
            Animate_Stop(Animation);
        }
        rc = SetupCopyError(
                IsWindow(Context->ProgressDialog) ? Context->ProgressDialog : Context->OwnerWindow,
                NULL,
                Context->CurrentSourceName,
                CopyError->Buffer,
                CopyError->Filename,
                CopyError->FilePaths->Target,
                CopyError->FilePaths->Win32Error,
                CopyError->Flags,
                CopyError->PathOut,
                MAX_PATH,
                NULL
                );
        if (Animation) {
            Animate_Play(Animation,0,-1,-1);
        }
        break;

    case UI_DELETEERROR:
        FilePaths = (PFILEPATHS)UiParameters;

        if (Animation) {
            Animate_Stop(Animation);
        }
        rc = SetupDeleteError(
                IsWindow(Context->ProgressDialog) ? Context->ProgressDialog : Context->OwnerWindow,
                NULL,
                FilePaths->Target,
                FilePaths->Win32Error,
                0
                );
        if (Animation) {
            Animate_Play(Animation,0,-1,-1);
        }

        break;

    case UI_RENAMEERROR:
        FilePaths = (PFILEPATHS)UiParameters;

        if(Animation) {
            Animate_Stop(Animation);
        }
        rc = SetupRenameError(
                IsWindow(Context->ProgressDialog) ? Context->ProgressDialog : Context->OwnerWindow,
                NULL,
                FilePaths->Source,
                FilePaths->Target,
                FilePaths->Win32Error,
                0
                );
        if(Animation) {
            Animate_Play(Animation,0,-1,-1);
        }

        break;

    case UI_BACKUPERROR:
        FilePaths = (PFILEPATHS)UiParameters;

        if(Animation) {
            Animate_Stop(Animation);
        }
        rc = SetupBackupError(
                IsWindow(Context->ProgressDialog) ? Context->ProgressDialog : Context->OwnerWindow,
                NULL,
                FilePaths->Source,
                FilePaths->Target,
                FilePaths->Win32Error,
                0
                );
        if(Animation) {
            Animate_Play(Animation,0,-1,-1);
        }

        break;

    case UI_NEEDMEDIA:
        NeedMedia = (PNEEDMEDIAUI)UiParameters;

        if(Animation) {
            Animate_Stop(Animation);
        }
        rc = SetupPromptForDisk(
                IsWindow(Context->ProgressDialog) ? Context->ProgressDialog : Context->OwnerWindow,
                NULL,
                NeedMedia->SourceMedia->Description,
                NeedMedia->SourceMedia->SourcePath,
                NeedMedia->SourceMedia->SourceFile,
                NeedMedia->SourceMedia->Tagfile,
                NeedMedia->Flags,
                NeedMedia->PathOut,
                MAX_PATH,
                NULL
                );
        if(Animation) {
            Animate_Play(Animation,0,-1,-1);
        }

        break;

    case UI_MISMATCHERROR:
        if(Animation) {
            Animate_Stop(Animation);
        }
        if(GlobalSetupFlags & (PSPGF_NONINTERACTIVE|PSPGF_UNATTENDED_SETUP)) {
            rc = DPROMPT_CANCEL;
        } else {
            rc = DialogBoxParam(
                     MyDllModuleHandle,
                     MAKEINTRESOURCE(IDD_REPLACE),
                     IsWindow(Context->ProgressDialog) ?
                        Context->ProgressDialog : Context->OwnerWindow,
                     pNotificationVersionDlgProc,
                     (LPARAM)UiParameters
                     );
        }
        if(Animation) {
            Animate_Play(Animation,0,-1,-1);
        }
        break;

    default:
        MYASSERT (0);
        rc = 0;
    }

    return rc;
}


INT_PTR
pSetupProgressDlgProc(
    IN HWND   hdlg,
    IN UINT   msg,
    IN WPARAM wParam,
    IN LPARAM lParam
    )
{
    BOOL b;
    PQUEUECONTEXT Context;
    HWND hwnd;
    PTSTR p;
    INT_PTR i;
    MSG m;
    BOOL Cancelled;
    HANDLE h;
    static UINT uQueryCancelAutoPlay = 0;

    switch(msg) {

    case WM_INITDIALOG:

#ifdef PRERELEASE
        if (GuiSetupInProgress) {
            MYASSERT( FALSE && TEXT("bringing up file progress dialog (IDD_FILEPROGRESS) during gui-mode setup, which is a UI violation.  Click yes and retrive a stack trace to determine errant caller.\n"));
        }
#endif

        Context = (PQUEUECONTEXT)lParam;
        MYASSERT(Context != NULL);
        SetProp(hdlg,DialogPropName,(HANDLE)Context);

        #ifdef NOCANCEL_SUPPORT
        //
        // If cancel is not allowed, disable the cancel button.
        //
        if(!Context->AllowCancel) {

            RECT rect;
            RECT rect2;

            hwnd = GetDlgItem(hdlg,IDCANCEL);

            ShowWindow(hwnd,SW_HIDE);
            EnableWindow(hwnd,FALSE);

            GetWindowRect(hdlg,&rect);
            GetWindowRect(hwnd,&rect2);

            SetWindowPos(
                hdlg,
                NULL,
                0,0,
                rect.right - rect.left,
                (rect.bottom - rect.top) - (rect.bottom - rect2.top),
                SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOZORDER
                );
        }
#endif

        //
        // Center the progress dialog relative to the parent window.
        //
        pSetupCenterWindowRelativeToParent(hdlg);

        SetFocus(GetDlgItem(hdlg,IDCANCEL));

        //
        // The main thread is processing SPFILENOTIFY_STARTQUEUE and is
        // waiting for some notification about the state of the UI thread.
        // Let the main thread know we succeeded, and pass back a real
        // handle to this thread that can be used to wait for it to terminate.
        //
        b = DuplicateHandle(
                GetCurrentProcess(),    // source process
                GetCurrentThread(),     // source handle
                GetCurrentProcess(),    // target process
                &h,                     // new handle
                0,                      // ignored with DUPLICATE_SAME_ACCESS
                FALSE,                  // not inheritable
                DUPLICATE_SAME_ACCESS
                );

        if(!b) {
            //
            // Since we cannot duplicate the handle, we must kill this dialog,
            // because otherwise the inter-thread communication will be broken
            // and things go horribly wrong.
            //
            EndDialog(hdlg, -1);
            return TRUE;            // TRUE or FALSE, it really doesn't matter
        }

        //
        // Store the dialog and progress bar handles in the context structure.
        //
        Context->ProgressDialog = hdlg;
        Context->ProgressBar = GetDlgItem(hdlg,IDC_PROGRESS);

        Context->UiThreadHandle = h;
        PostMessage(hdlg,WMX_HELLO,0,0); // put WMX_HELLO on our message queue
        SetEvent(Context->hEvent); // inform caller we've done initialization

        b = FALSE;
        break;

    case WMX_HELLO:
        //
        // If this guy has no owner force him to the foreground.
        // This catches cases where people are using a series of
        // dialogs and then some setup apis, because when they
        // close a dialog focus switches away from them.
        //
        hwnd = GetWindow(hdlg,GW_OWNER);
        if(!IsWindow(hwnd)) {
            SetForegroundWindow(hdlg);
        }

        b = TRUE;

        break;

    case WMX_PERFORMUI:
        b = TRUE;
        Context = (PQUEUECONTEXT)GetProp(hdlg,DialogPropName);
        MYASSERT(Context != NULL);

        //
        // We'd better not already have any UI pending...
        //
        MYASSERT(Context->PendingUiType == UI_NONE);

        if (Context->MessageBoxUp == TRUE) {
            Context->PendingUiType = LOWORD (wParam);
            Context->CancelReturnCode = HIWORD (wParam);
            Context->PendingUiParameters = (PVOID)lParam;
        } else {
            Context->lParam = pPerformUi (Context, LOWORD(wParam), (PVOID)lParam);
            SetEvent(Context->hEvent); // wakeup main thread (lParam has Ui result)
        }

        break;

    case WM_COMMAND:
        Context = (PQUEUECONTEXT)GetProp(hdlg,DialogPropName);
        MYASSERT(Context != NULL);
        if((HIWORD(wParam) == BN_CLICKED) && (LOWORD(wParam) == IDCANCEL)) {
            p = MyLoadString(IDS_CANCELFILEOPS);
            Cancelled = FALSE;
            if(p) {
                //
                // While the message box is up, the main thread is still copying files,
                // and it might just complete. If that happens, the main thread will
                // post us WMX_KILLDIALOG, which would cause this dialog to nuke itself
                // out from under the message box. The main thread would then continue
                // executing while the message box is sitting there. Some components
                // actually unload setupapi.dll at that point, and so when the user
                // dismisses the message box, an AV results.
                //
                // We can't freeze the main thread via SuspendThread because then
                // that thread, which probably owns this dialog's parent window,
                // will not be able to process messages that result from the message box's
                // creation. Result is that the message box never comes up and the process
                // is deadlocked.
                //
                // We'd better not already have a message box up!
                //
                MYASSERT(!Context->MessageBoxUp);

                Context->MessageBoxUp = TRUE;
                i = MessageBox(
                        hdlg,
                        p,
                        TEXT(""),
                        MB_YESNO | MB_APPLMODAL | MB_DEFBUTTON2 | MB_SETFOREGROUND | MB_ICONQUESTION
                        );

                Context->MessageBoxUp = FALSE;

                //
                // We set b to TRUE if the dialog is going away.
                // We set Cancelled to TRUE if the user clicked the CANCEL button.
                //
                if(Context->DialogKilled) {
                    b = TRUE;
                    Cancelled = (i == IDYES);
                } else {
                    b = (i == IDYES);
                    Cancelled = b;
                }
                MyFree(p);
            } else {
                pSetupOutOfMemory(hdlg);
                Cancelled = TRUE;
                b = TRUE;
            }

            if(b) {
                if(Cancelled) {
                    Context->Cancelled = TRUE;
                }
                PostMessage(hdlg,WMX_KILLDIALOG,0,0);

                if (Context->PendingUiType != UI_NONE) {

                    //
                    // We now allow the main thread to continue.  Once we do
                    // so, the UI parameters that we passed to us are invalid.
                    // Cancel the pending UI.
                    //
                    Context->PendingUiType = UI_NONE;
                    Context->lParam = Context->CancelReturnCode;
                    SetEvent(Context->hEvent); // wake up main thread (lParam has UI result)
                }

            } else {
                if (Context->PendingUiType != UI_NONE) {
                    Context->lParam = pPerformUi(Context,
                                                 (UINT)Context->PendingUiType,
                                                 Context->PendingUiParameters);

                    Context->PendingUiType = UI_NONE;
                    SetEvent(Context->hEvent); // wake up main thread (lParam has UI result)
                }
            }
            b = TRUE;
        } else {
            b = FALSE;
        }
        break;

    case WMX_KILLDIALOG:
        //
        // Exit unconditionally. Clean up first.
        //
        b = TRUE;
        Context = (PQUEUECONTEXT)GetProp(hdlg, DialogPropName);
        MYASSERT(Context != NULL);
        if(Context->MessageBoxUp) {
            //
            // The user was still interacting with the "are you sure you
            // want to cancel" dialog and the copying finished. So we don't want
            // to nuke the dialog out from under the message box.
            //
            Context->DialogKilled = TRUE;
            break;
        }

        DestroyWindow(Context->ProgressBar);
        EndDialog(hdlg, 0);

        break;

    default:
        //
        // we disable autorun because it confuses the user.
        //
        if (!uQueryCancelAutoPlay) {
            uQueryCancelAutoPlay = RegisterWindowMessage(TEXT("QueryCancelAutoPlay"));
        }

        if (msg == uQueryCancelAutoPlay) {
            SetWindowLongPtr( hdlg, DWLP_MSGRESULT, 1 );
            return 1;       // cancel auto-play
        }

        b = FALSE;
        break;
    }

    return(b);
}


PVOID
SetupInitDefaultQueueCallbackEx(
    IN HWND  OwnerWindow,
    IN HWND  AlternateProgressWindow, OPTIONAL
    IN UINT  ProgressMessage,
    IN DWORD Reserved1,
    IN PVOID Reserved2
    )
{
    PQUEUECONTEXT Context;
    BOOL b;

    Context = MyMalloc(sizeof(QUEUECONTEXT));
    if(Context) {
        ZeroMemory(Context,sizeof(QUEUECONTEXT));

        Context->Signature = QUEUECONTEXT_SIGNATURE;
        Context->hEvent = CreateEvent(NULL,FALSE,FALSE,NULL);
        if (Context->hEvent == NULL) {
            MyFree(Context);
            return NULL;
        }
        Context->OwnerWindow = OwnerWindow;
        Context->MainThreadId = GetCurrentThreadId();
        Context->ProgressMsg = ProgressMessage;
        Context->NoToAllMask = 0;

        //
        // If the caller specified NULL for the alternate progress window, and
        // we're running non-interactively, we want to treat this just as if
        // they'd specified INVALID_HANDLE_VALUE (i.e., suppress all progress
        // UI).
        //
        if((GlobalSetupFlags & PSPGF_NONINTERACTIVE) && !AlternateProgressWindow) {
            Context->AlternateProgressWindow = INVALID_HANDLE_VALUE;
        } else {
            Context->AlternateProgressWindow = AlternateProgressWindow;
        }

        if(SystemParametersInfo(SPI_GETSCREENREADER,0,&b,0) && b) {
            Context->ScreenReader = TRUE;
        } else {
            Context->ScreenReader = FALSE;
        }

#ifdef PRERELEASE
        //
        // if we're running in gui-mode setup, we suppress all setupapi progress UI
        // and only allow AlternateProgressWindow UI
        //
        if (GuiSetupInProgress
            && (Context->AlternateProgressWindow != (HWND)INVALID_HANDLE_VALUE)
            && !IsWindow(Context->AlternateProgressWindow)) {
            MYASSERT( FALSE && TEXT("SetupInitDefaultQueueCallbackEx() called in gui-setup without INVALID_HANDLE_VALUE, which means UI may be presented.  Click yes and retrieve the stack trace to detect errant caller.\n"));
        }
#endif

    }

    return(Context);
}


PVOID
SetupInitDefaultQueueCallback(
    IN HWND OwnerWindow
    )
{
#ifdef PRERELEASE
    if (GuiSetupInProgress) {
        MYASSERT( FALSE && TEXT("SetupInitDefaultQueueCallback() called in gui-setup, which means UI may be presented.  Click yes and retrieve the stack trace to detect the errant caller.\n"));
    }
#endif
    return(SetupInitDefaultQueueCallbackEx(OwnerWindow,NULL,0,0,NULL));
}


VOID
SetupTermDefaultQueueCallback(
    IN PVOID Context
    )
{
    PQUEUECONTEXT context;

    context = Context;

    try {
        if(context && context->Signature == QUEUECONTEXT_SIGNATURE) {
            if(context->CurrentSourceName) {
                MyFree(context->CurrentSourceName);
            }
            if (context->hEvent) {
                CloseHandle(context->hEvent);
            }
            context->Signature = 0;
        }
        MyFree(Context);
    } except(EXCEPTION_EXECUTE_HANDLER) {
        ;
    }
}


#ifdef UNICODE
//
// ANSI version
//
UINT
SetupDefaultQueueCallbackA(
    IN PVOID Context,
    IN UINT  Notification,
    IN UINT_PTR Param1,
    IN UINT_PTR Param2
    )
{
    UINT u;

    u = pSetupCallDefaultMsgHandler(
            Context,
            Notification,
            Param1,
            Param2
            );

    return(u);
}
#else
//
// Unicode stub
//
UINT
SetupDefaultQueueCallbackW(
    IN PVOID Context,
    IN UINT  Notification,
    IN UINT_PTR Param1,
    IN UINT_PTR Param2
    )
{
    UNREFERENCED_PARAMETER(Context);
    UNREFERENCED_PARAMETER(Notification);
    UNREFERENCED_PARAMETER(Param1);
    UNREFERENCED_PARAMETER(Param2);
    SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
    return(0);
}
#endif

UINT
SetupDefaultQueueCallback(
    IN PVOID Context,
    IN UINT  Notification,
    IN UINT_PTR Param1,
    IN UINT_PTR Param2
    )
{
    UINT rc;
    DWORD err;
    PQUEUECONTEXT context = Context;
    MSG msg;
    VERDLGCONTEXT dialogContext;
    DWORD waitResult;

    //
    // a little sanity check on context
    //
    err = NO_ERROR;
    try {
        if (context == NULL || context->Signature != QUEUECONTEXT_SIGNATURE) {
            err = ERROR_INVALID_PARAMETER;
        }
    } except(EXCEPTION_EXECUTE_HANDLER) {
        err = ERROR_INVALID_PARAMETER;
    }
    if (err != NO_ERROR) {
        return pGetCallbackErrorReturn(Notification,err);
    }

    switch(Notification) {

    case SPFILENOTIFY_STARTQUEUE:
        rc = pNotificationStartQueue(context);
        break;

    case SPFILENOTIFY_ENDQUEUE:
        //
        // Make sure the progress dialog is dead.
        //
        if(context->AlternateProgressWindow) {
            //
            // If we have an alternate progress window, then we'd better not
            // have our own progress dialog, nor should we have a UI thread
            // handle.
            //
            MYASSERT(!context->ProgressDialog);
            MYASSERT(!context->UiThreadHandle);

        } else {

            if(IsWindow(context->ProgressDialog)) {
                //
                // Post a message to the dialog, instructing it to terminate,
                // then wait for it to confirm.
                //
                PostMessage(context->ProgressDialog, WMX_KILLDIALOG, 0, 0);
            }
            //
            // The dialog may have been marked for delete (thus IsWindow() fails),
            // and yet the dialog has not yet been destroyed.  Therefore, we always
            // want to wait for the thread message that assures us that everything
            // has been cleaned up in the other thread.
            //
            // Also, before returning we need to make sure that the UI thread is
            // really gone, or else there's a hole where our caller could unload
            // the library and then the UI thread would fault.
            // We have seen this happen in stress.
            //
            while (pWaitForUiResponse(context)) /* nothing */;
            if(context->UiThreadHandle) {
                waitResult = WaitForSingleObject(context->UiThreadHandle,INFINITE);
                MYASSERT(waitResult != WAIT_FAILED);
                CloseHandle(context->UiThreadHandle);
            }
        }
        rc = TRUE;  // return value for this notification is actually ignored.
        break;

    case SPFILENOTIFY_STARTSUBQUEUE:
        rc = pNotificationStartEndSubqueue(context,TRUE,Param1,Param2);
        break;

    case SPFILENOTIFY_ENDSUBQUEUE:
        rc = pNotificationStartEndSubqueue(context,FALSE,Param1,0);
        break;

    case SPFILENOTIFY_STARTDELETE:
    case SPFILENOTIFY_STARTRENAME:
    case SPFILENOTIFY_STARTCOPY:
    case SPFILENOTIFY_STARTBACKUP:
        //
        // Update display to indicate the files involved
        // in the operation, unless a screen reader is active.
        //
        if(context->ScreenReader) {
            rc = FILEOP_DOIT;
        } else {
            rc = pNotificationStartOperation(context,(PFILEPATHS)Param1,Param2);
        }
        break;

    case SPFILENOTIFY_ENDDELETE:
    case SPFILENOTIFY_ENDRENAME:
    case SPFILENOTIFY_ENDCOPY:
    case SPFILENOTIFY_ENDBACKUP:
    case SPFILENOTIFY_ENDREGISTRATION:

        if(context->AlternateProgressWindow) {
            //
            // If this is really is an alternate progress window, then 'tick' it.
            // Copy only.
            //
            if((Notification == SPFILENOTIFY_ENDCOPY) &&
               (context->AlternateProgressWindow != INVALID_HANDLE_VALUE)) {

                SendMessage(context->AlternateProgressWindow, context->ProgressMsg, 1, 0);
            }
        } else {
            if(IsWindow(context->ProgressBar)) {
                //
                // Update gas gauge.
                //
                SendMessage(context->ProgressBar,PBM_STEPIT,0,0);
            }
        }
        rc = TRUE;  // return value for these notifications is actually ignored.
        break;

    case SPFILENOTIFY_DELETEERROR:
        rc = pNotificationErrorDelete(context,(PFILEPATHS)Param1);
        break;

    case SPFILENOTIFY_RENAMEERROR:
        rc = pNotificationErrorRename(context,(PFILEPATHS)Param1);
        break;

    case SPFILENOTIFY_BACKUPERROR:
        rc = pNotificationErrorBackup(context,(PFILEPATHS)Param1);
        break;

    case SPFILENOTIFY_COPYERROR:
        rc = pNotificationErrorCopy(context,(PFILEPATHS)Param1,(PTSTR)Param2);
        break;

    case SPFILENOTIFY_NEEDMEDIA:
        //
        // Perform prompt.
        //
        rc = pNotificationNeedMedia(context,(PSOURCE_MEDIA)Param1,(PTSTR)Param2);
        break;

    case SPFILENOTIFY_STARTREGISTRATION:
        rc = pNotificationStartRegistration(context,(PSP_REGISTER_CONTROL_STATUS)Param1,(BOOL)Param2);
        break;

    default:
        //
        // The notification is either an unknown ordinal or a version mismatch.
        //
        if(Notification & (SPFILENOTIFY_LANGMISMATCH | SPFILENOTIFY_TARGETNEWER | SPFILENOTIFY_TARGETEXISTS)) {
            //
            // It's one or more of our known version mismatches.  First
            // check to see whether No to All has already been specified
            // for the mismatch(es).  Turn off the bits in the notification
            // that are set in NoToAllMask; if there are still bits set,
            // we need to notify about this mismatch.  If there are no
            // longer any bits set, then don't copy this file.
            //
            Notification &= ~context->NoToAllMask;
            if (Notification != 0) {
                //
                // Notify about this mismatch.
                //
                dialogContext.QueueContext = context;
                dialogContext.Notification = Notification;
                dialogContext.Param1 = Param1;
                dialogContext.Param2 = Param2;
                rc = PostUiMessage (
                    context, UI_MISMATCHERROR, DPROMPT_CANCEL, &dialogContext);
                rc = (rc == IDYES);
            } else {
                //
                // No To All has already been specified for this notification type.
                // Skip the file.
                //
                rc = 0;
            }

        } else {
            //
            // Unknown notification. Skip the file.
            //
            rc = 0;
        }
        break;
    }

    return(rc);
}