diff --git a/base/setup/reactos/reactos.c b/base/setup/reactos/reactos.c index e974daac9b3..e3df176ac61 100644 --- a/base/setup/reactos/reactos.c +++ b/base/setup/reactos/reactos.c @@ -36,6 +36,7 @@ PPARTENTRY SystemPartition = NULL; /* UI elements */ UI_CONTEXT UiContext; +HCURSOR hWaitCursor; /* FUNCTIONS ****************************************************************/ @@ -1713,7 +1714,7 @@ FileCopyCallback(PVOID Context, PCWSTR SrcFileName, DstFileName; WaitForSingleObject(CopyContext->pSetupData->hHaltInstallEvent, INFINITE); - if (CopyContext->pSetupData->bStopInstall) + if (CopyContext->pSetupData->bAbortInstall) return FILEOP_ABORT; // Stop committing files switch (Notification) @@ -1883,6 +1884,9 @@ PropSheet_SetCloseCancel( MF_BYCOMMAND | (Enable ? MF_ENABLED : MF_GRAYED)); } +#define PM_INSTALL_START (WM_APP + 1) +#define PM_INSTALL_DONE (WM_APP + 2) + static DWORD WINAPI PrepareAndDoCopyThread( @@ -1916,7 +1920,6 @@ PrepareAndDoCopyThread( /* Disable the Close/Cancel buttons during all partition operations */ // TODO: Consider, alternatively, to just show an info-box saying // that the installation process cannot be canceled at this stage? - // PropSheet_SetWizButtons(hWndParent, 0); PropSheet_SetCloseCancel(hWndParent, FALSE); @@ -1951,13 +1954,7 @@ PrepareAndDoCopyThread( /* Re-enable the Close/Cancel buttons */ PropSheet_SetCloseCancel(hWndParent, TRUE); - - /* - * We failed due to an unexpected error, keep on the copy page to view the current state, - * but enable the "Next" button to allow the user to continue to the Abort page. - */ - PropSheet_SetWizButtons(hWndParent, PSWIZB_NEXT); - return 1; + goto Quit; } @@ -1980,13 +1977,7 @@ PrepareAndDoCopyThread( /* Re-enable the Close/Cancel buttons */ PropSheet_SetCloseCancel(hWndParent, TRUE); - - /* - * We failed due to an unexpected error, keep on the copy page to view the current state, - * but enable the "Next" button to allow the user to continue to the Abort page. - */ - PropSheet_SetWizButtons(hWndParent, PSWIZB_NEXT); - return 1; + goto Quit; } @@ -1994,26 +1985,19 @@ PrepareAndDoCopyThread( PropSheet_SetCloseCancel(hWndParent, TRUE); - /* Re-calculate the final destination paths */ ASSERT(InstallPartition); Status = InitDestinationPaths(&pSetupData->USetupData, NULL, // pSetupData->USetupData.InstallationDirectory, InstallVolume); - if (!NT_SUCCESS(Status)) + Success = NT_SUCCESS(Status); + if (!Success) { DisplayMessage(hWndParent, MB_ICONERROR, NULL, L"InitDestinationPaths() failed with status 0x%08lx\n", Status); - - /* - * We failed due to an unexpected error, keep on the copy page to view the current state, - * but enable the "Next" button to allow the user to continue to the Abort page. - */ - PropSheet_SetWizButtons(hWndParent, PSWIZB_NEXT); - return 1; + goto Quit; } - /* * Preparation of the list of files to be copied */ @@ -2039,17 +2023,9 @@ PrepareAndDoCopyThread( if (/*ErrorNumber != ERROR_SUCCESS*/ !Success) { /* Display an error only if an unexpected failure happened, and not because the user cancelled the installation */ - if (!pSetupData->bStopInstall) + if (!pSetupData->bAbortInstall) MessageBoxW(hWndParent, L"Failed to prepare the list of files!", NULL, MB_ICONERROR); - - /* - * If we failed due to an unexpected error, keep on the copy page to view the current state, - * but enable the "Next" button to allow the user to continue to the Abort page. - * Otherwise we have been cancelled by the user, who has already switched to the Abort page. - */ - if (!pSetupData->bStopInstall) - PropSheet_SetWizButtons(hWndParent, PSWIZB_NEXT); - return 1; + goto Quit; } @@ -2069,20 +2045,13 @@ PrepareAndDoCopyThread( CopyContext.CompletedOperations = 0; /* Do the file copying - The callback handles whether or not we should stop file copying */ - if (!DoFileCopy(&pSetupData->USetupData, FileCopyCallback, &CopyContext)) + Success = DoFileCopy(&pSetupData->USetupData, FileCopyCallback, &CopyContext); + if (!Success) { /* Display an error only if an unexpected failure happened, and not because the user cancelled the installation */ - if (!pSetupData->bStopInstall) + if (!pSetupData->bAbortInstall) MessageBoxW(hWndParent, L"Failed to copy the files!", NULL, MB_ICONERROR); - - /* - * If we failed due to an unexpected error, keep on the copy page to view the current state, - * but enable the "Next" button to allow the user to continue to the Abort page. - * Otherwise we have been cancelled by the user, who has already switched to the Abort page. - */ - if (!pSetupData->bStopInstall) - PropSheet_SetWizButtons(hWndParent, PSWIZB_NEXT); - return 1; + goto Quit; } // /* Set status text */ @@ -2095,6 +2064,11 @@ PrepareAndDoCopyThread( InstallSetupInfFile(&pSetupData->USetupData); + /* Disable the Close/Cancel buttons for the remaining non-cancellable operations */ + // TODO: Consider, alternatively, to just show an info-box saying + // that the installation process cannot be canceled at this stage? + PropSheet_SetCloseCancel(hWndParent, FALSE); + /* * Create or update the registry hives */ @@ -2251,7 +2225,8 @@ PrepareAndDoCopyThread( IDS_ERROR_BOOTLDR_FAILED, Status); } - break; + Success = FALSE; + goto Quit; } /* Skip installation */ @@ -2260,12 +2235,14 @@ PrepareAndDoCopyThread( break; } - - /* We are done! Switch to the Finish page */ - PropSheet_SetCurSelByID(hWndParent, IDD_FINISHPAGE); - return 0; +Quit: + /* Signal the wizard page that we have succeeded or failed */ + PostMessage(hwndDlg, PM_INSTALL_DONE, Success, 0); // Status + return Success; } +// TODO: Determine later which method is the best. +//#define TERMINATE_USE_PRESSBUTTON static INT_PTR CALLBACK ProcessDlgProc( @@ -2294,6 +2271,16 @@ ProcessDlgProc( break; } + case WM_SETCURSOR: + { + /* Set a page-wide cursor */ + if (!hWaitCursor) + break; + SetCursor(hWaitCursor); + SetWindowLongPtrW(hwndDlg, DWLP_MSGRESULT, TRUE); + return TRUE; + } + case WM_NOTIFY: { LPNMHDR lpnm = (LPNMHDR)lParam; @@ -2304,83 +2291,256 @@ ProcessDlgProc( { HWND hWndParent = GetParent(hwndDlg); - /* Create the file-copy halt (manual-reset) event */ - pSetupData->hHaltInstallEvent = CreateEventW(NULL, TRUE, TRUE, NULL); - if (!pSetupData->hHaltInstallEvent) - break; - pSetupData->bStopInstall = FALSE; + /* + * Disable all buttons during installation, and hide "Back" and "Next". + * Don't use the PropSheet_SetWizButtons() macro, because its + * posted message would be handled after hiding the buttons. + * The message would then interfere with the hidden buttons + * (when both "Back" and "Next" are hidden, "Next" gets forcefully shown). + */ + SendMessageW(hWndParent, PSM_SETWIZBUTTONS, 0, 0); + // PropSheet_ShowWizButtons(hWndParent, 0, PSWIZB_BACK | PSWIZB_NEXT); + ShowDlgItem(hWndParent, ID_WIZBACK, SW_HIDE); + ShowDlgItem(hWndParent, ID_WIZNEXT, SW_HIDE); - /* Start the prepare-and-copy files thread */ - pSetupData->hInstallThread = - CreateThread(NULL, 0, - PrepareAndDoCopyThread, - (PVOID)hwndDlg, - CREATE_SUSPENDED, - NULL); - if (!pSetupData->hInstallThread) + /* Start the installation procedure once the page is fully displayed */ + PostMessageW(hwndDlg, PM_INSTALL_START, 0, 0); + break; + } + + case PSN_KILLACTIVE: + { + /* Avoid reentrant calls caused by the message dispatching + * done below: allow only the first page change, and ignore + * all the others. */ + if (InterlockedFlagsTestAndSet8(&pSetupData->bStopInstall, + SETUP_PAGE_SWITCHING)) { - CloseHandle(pSetupData->hHaltInstallEvent); - pSetupData->hHaltInstallEvent = NULL; - MessageBoxW(hWndParent, L"Cannot create the prepare-and-copy files thread!", NULL, MB_ICONERROR); - break; + SetWindowLongPtrW(hwndDlg, DWLP_MSGRESULT, TRUE); + return TRUE; } - /* Disable all buttons during installation, they will be - * re-enabled by the installation thread; hide "Back" */ - PropSheet_SetWizButtons(hWndParent, 0); - // PropSheet_ShowWizButtons(hWndParent, 0, PSWIZB_BACK); - ShowDlgItem(hWndParent, ID_WIZBACK, SW_HIDE); + /* If no installation is running, just change the page */ + if (!pSetupData->hInstallThread) + break; - /* Resume the installation thread */ - ResumeThread(pSetupData->hInstallThread); + /* Wait for the installation thread to terminate. + * Since we dispatch messages in the meantime, we must + * guard the property sheet notification handlers from + * being invoked multiple times. */ + DWORD dwStatus = WaitForSingleObject(pSetupData->hInstallThread, 0); + if (dwStatus == WAIT_TIMEOUT) + { + /* Show the hourglass cursor */ + HCURSOR hOldCursor; + hWaitCursor = LoadCursor(NULL, IDC_WAIT); + hOldCursor = SetCursor(hWaitCursor); + ShowCursor(TRUE); + + for (;;) + { + MSG msg; + dwStatus = MsgWaitForMultipleObjects(1, &pSetupData->hInstallThread, + FALSE, INFINITE, + QS_ALLINPUT | QS_ALLPOSTMESSAGE); + if (dwStatus != (WAIT_OBJECT_0 + 1)) + break; // Stop if anything else (thread terminated, timeout or failure). + + /* We still need to process main window messages to avoid freeze */ + while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) + { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + } + + /* Restore the old cursor */ + ShowCursor(FALSE); + SetCursor(hOldCursor); + hWaitCursor = NULL; + } +#if 0 && DBG + if (dwStatus == WAIT_OBJECT_0) + { + DWORD dwExitCode = NO_ERROR; + GetExitCodeThread(pSetupData->hInstallThread, &dwExitCode); + DBG_UNREFERENCED_PARAMETER(dwExitCode); + } +#endif + /* Cleanup; we can now change the page */ + CloseHandle(pSetupData->hInstallThread); + pSetupData->hInstallThread = NULL; + CloseHandle(pSetupData->hHaltInstallEvent); + pSetupData->hHaltInstallEvent = NULL; break; } case PSN_QUERYCANCEL: { - /* Halt the on-going file copy */ - ResetEvent(pSetupData->hHaltInstallEvent); + INT nRet; - if (DisplayMessage(GetParent(hwndDlg), - MB_ICONQUESTION | MB_YESNO | MB_DEFBUTTON2, - MAKEINTRESOURCEW(IDS_ABORTSETUP2), - MAKEINTRESOURCEW(IDS_ABORTSETUP)) == IDYES) + if (pSetupData->bStopInstall) { - /* Stop the file copy thread */ - pSetupData->bStopInstall = TRUE; - SetEvent(pSetupData->hHaltInstallEvent); - -#if 0 - /* Wait for any pending installation */ - WaitForSingleObject(pSetupData->hInstallThread, INFINITE); - CloseHandle(pSetupData->hInstallThread); - pSetupData->hInstallThread = NULL; - CloseHandle(pSetupData->hHaltInstallEvent); - pSetupData->hHaltInstallEvent = NULL; -#endif - - // TODO: Unwind installation?! - - /* Go to the Abort page */ - PropSheet_SetCurSelByID(GetParent(hwndDlg), IDD_ABORTPAGE); + /* The installation is terminating (either successfully, + * or cancelled by the user or due to an error), ignore all + * new requests since this is now too late to change bets. */ + nRet = IDYES; } else { - /* We don't stop installation, resume file copy */ + /* Halt the on-going file copy */ + ResetEvent(pSetupData->hHaltInstallEvent); + + nRet = DisplayMessage(GetParent(hwndDlg), + MB_ICONQUESTION | MB_YESNO | MB_DEFBUTTON2, + MAKEINTRESOURCEW(IDS_ABORTSETUP2), + MAKEINTRESOURCEW(IDS_ABORTSETUP)); + if (nRet == IDYES) + { + /* Signal the file copy thread to stop */ + InterlockedOr8((PCHAR)&pSetupData->bStopInstall, SETUP_ABORT_INSTALL); + } + /* else, we don't stop installation, resume file copy */ SetEvent(pSetupData->hHaltInstallEvent); } + /* If we are not already in the process of cancelling + * the installation or switching to a page, do it now. */ + if ((nRet == IDYES) && !pSetupData->bPageSwitching && + !InterlockedFlagsTestAndSet8(&pSetupData->bStopInstall, + SETUP_IS_CANCELLING)) + { + /* Go to the Abort page */ +#ifdef TERMINATE_USE_PRESSBUTTON + PropSheet_SetCloseCancel(GetParent(hwndDlg), FALSE); + PropSheet_SetCurSelByID(GetParent(hwndDlg), IDD_ABORTPAGE); +#else + PostMessageW(hwndDlg, PM_INSTALL_DONE, FALSE, 0); +#endif + } + /* Do not close the wizard too soon */ SetWindowLongPtrW(hwndDlg, DWLP_MSGRESULT, TRUE); return TRUE; } + case PSN_WIZBACK: + /* Always disable going back */ + SetWindowLongPtrW(hwndDlg, DWLP_MSGRESULT, -1); + return TRUE; + + case PSN_WIZNEXT: + { +#ifdef TERMINATE_USE_PRESSBUTTON + LONG_PTR nNextPage = (!pSetupData->bAbortInstall ? IDD_FINISHPAGE : IDD_ABORTPAGE); + SetWindowLongPtrW(hwndDlg, DWLP_MSGRESULT, nNextPage); +#else + /* Always disable going next */ + SetWindowLongPtrW(hwndDlg, DWLP_MSGRESULT, -1); +#endif + return TRUE; + } + default: break; } break; } + case PM_INSTALL_START: + { + HWND hWndParent = GetParent(hwndDlg); + + ASSERT(pSetupData->hInstallThread == NULL); + + /* Force repainting first to make sure the wizard is visible */ + InvalidateRect(hWndParent, NULL, FALSE); + UpdateWindow(hWndParent); + + /* Create the file-copy halt (manual-reset) event */ + pSetupData->hHaltInstallEvent = CreateEventW(NULL, TRUE, TRUE, NULL); + if (!pSetupData->hHaltInstallEvent) + { + DisplayMessage(hWndParent, MB_ICONERROR, NULL, + L"Cannot create the install event, error %lu\n", GetLastError()); + goto Fail; + } + /* Start the installation thread */ + pSetupData->bStopInstall = FALSE; + pSetupData->hInstallThread = CreateThread(NULL, 0, + PrepareAndDoCopyThread, + (PVOID)hwndDlg, + 0, NULL); + if (!pSetupData->hInstallThread) + { + DisplayMessage(hWndParent, MB_ICONERROR, NULL, + L"Cannot create the installation thread, error %lu\n", GetLastError()); + CloseHandle(pSetupData->hHaltInstallEvent); + pSetupData->hHaltInstallEvent = NULL; + Fail: + /* Cancel the installation */ + InterlockedOr8((PCHAR)&pSetupData->bStopInstall, SETUP_ABORT_INSTALL); +#ifdef TERMINATE_USE_PRESSBUTTON + PropSheet_SetCloseCancel(hWndParent, FALSE); + PropSheet_PressButton(hWndParent, PSBTN_CANCEL); +#else + SendMessageW(hwndDlg, PM_INSTALL_DONE, FALSE, 0); +#endif + } + break; + } + + case PM_INSTALL_DONE: + { + HWND hWndParent = GetParent(hwndDlg); + BOOL Success = !!wParam; + BOOL AutoSwitchPage = TRUE; + + /* If an unexpected error happened, stay on the copy page to allow + * the user to view the current state, and keep the Close/Cancel + * buttons to allow going to the Abort page. + * Otherwise, we have been manually cancelled by the user and we + * will directly switch to the Abort page. */ + if (!Success && !pSetupData->bStopInstall) + { + PropSheet_SetCloseCancel(hWndParent, TRUE); + AutoSwitchPage = FALSE; + } + else + { + /* Disable the Close/Cancel buttons, since the installation has + * either successfully terminated, or was already cancelled by + * the user. */ + PropSheet_SetCloseCancel(hWndParent, FALSE); + } + + if (!Success) + { + /* The installation was aborted */ + InterlockedOr8((PCHAR)&pSetupData->bStopInstall, SETUP_ABORT_INSTALL); + } + else if (pSetupData->bAbortInstall) + { + /* Override success in case the thread just terminated with some status + * while at the same time, the user chose to cancel the installation */ + Success = FALSE; + } + + /* We are done! Switch to the Finish or the Abort page */ + if (!AutoSwitchPage) + break; + // TODO: if (!Success): Unwind installation. +#ifdef TERMINATE_USE_PRESSBUTTON + // NOTE: In this case, PSN_QUERYCANCEL **CANNOT** call PM_INSTALL_DONE + PropSheet_PressButton(hWndParent, Success ? PSBTN_NEXT : PSBTN_CANCEL); +#else + if (!Success) + InterlockedOr8((PCHAR)&pSetupData->bStopInstall, SETUP_IS_CANCELLING); + PropSheet_SetCurSelByID(hWndParent, Success ? IDD_FINISHPAGE : IDD_ABORTPAGE); +#endif + break; + } + default: break; } @@ -3298,6 +3458,19 @@ PrshtWndProc(HWND hWnd, UINT uMessage, WPARAM wParam, LPARAM lParam) break; } + case WM_SETCURSOR: + { + /* Set a wizard-wide cursor */ + // NOTE: There is a problem, where when hovering over the wizard + // navigation buttons, the cursor would blink with the arrow. + // To mitigate this problem, only show the custom cursor when + // we are on the main wizard window. + if (!(hWaitCursor && (wParam == (WPARAM)hWnd))) + break; + SetCursor(hWaitCursor); + return TRUE; + } + case WM_DESTROY: { /* Restore the original dialog procedure */ @@ -3538,13 +3711,6 @@ _tWinMain(HINSTANCE hInst, /* Display the wizard */ PropertySheetW(&psh); - /* Wait for any pending installation */ - WaitForSingleObject(SetupData.hInstallThread, INFINITE); - CloseHandle(SetupData.hInstallThread); - SetupData.hInstallThread = NULL; - CloseHandle(SetupData.hHaltInstallEvent); - SetupData.hHaltInstallEvent = NULL; - if (SetupData.hBoldFont) DeleteFont(SetupData.hBoldFont); if (SetupData.hTitleFont) diff --git a/base/setup/reactos/reactos.h b/base/setup/reactos/reactos.h index 366b4e64ca4..78a0b7687c7 100644 --- a/base/setup/reactos/reactos.h +++ b/base/setup/reactos/reactos.h @@ -115,6 +115,12 @@ typedef struct _KBLAYOUT } KBLAYOUT, *PKBLAYOUT; #endif +#define InterlockedFlagsTestAndSet8(Target, Flags) \ + !!(InterlockedOr8((PCHAR)(Target), (Flags)) & (Flags)) + +#define InterlockedFlagsTestAndReset8(Target, Flags) \ + !!(InterlockedAnd8((PCHAR)(Target), ~(Flags)) & (Flags)) + typedef struct _SETUPDATA { /* General */ @@ -127,7 +133,23 @@ typedef struct _SETUPDATA HANDLE hInstallThread; HANDLE hHaltInstallEvent; - BOOL bStopInstall; + union + { + struct + { +#define SETUP_ABORT_INSTALL 1 + UCHAR bAbortInstall :2; /**< TRUE if the installation is to be aborted; + ** FALSE if not (regular termination). */ +#define SETUP_IS_CANCELLING 4 + UCHAR bIsCancelling :2; /**< TRUE if the installation is being cancelled + ** (via PSN_QUERYCANCEL); FALSE if not. */ +#define SETUP_PAGE_SWITCHING 16 + UCHAR bPageSwitching :2; /**< TRUE if a page switch is occurring + ** (PSN_KILLACTIVE running); FALSE if not. */ + UCHAR bReserved :2; + }; + BOOLEAN bStopInstall; + }; NT_WIN32_PATH_MAPPING_LIST MappingList;