123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472 |
- unit Setup.MainForm;
- {
- Inno Setup
- Copyright (C) 1997-2025 Jordan Russell
- Portions by Martijn Laan
- For conditions of distribution and use, see LICENSE.TXT.
- }
- interface
- uses
- Windows, SysUtils, Classes,
- Shared.SetupSteps;
- type
- TMainForm = class(TComponent)
- private
- class procedure AppOnGetActiveFormHandle(var AHandle: HWND);
- public
- CurStep: TSetupStep;
- destructor Destroy; override;
- procedure Close;
- procedure Finish(const FromPreparingPage: Boolean);
- function Install: Boolean;
- procedure SetStep(const AStep: TSetupStep; const HandleExceptions: Boolean);
- class procedure ShowException(Sender: TObject; E: Exception);
- class procedure ShowExceptionMsg(const S: String);
- end;
- var
- MainForm: TMainForm;
- implementation
- uses
- Messages, ShlObj,
- Forms,
- SHA256, RestartManager,
- Shared.Struct, Shared.CommonFunc, Shared.CommonFunc.Vcl, Shared.SetupMessageIDs,
- SetupLdrAndSetup.Messages, Setup.Install,
- Setup.MainFunc, Setup.InstFunc, Setup.WizardForm, Setup.LoggingFunc, Shared.SetupTypes;
- destructor TMainForm.Destroy;
- begin
- MainForm := nil; { just to detect use-after-free }
- inherited;
- end;
- class procedure TMainForm.ShowExceptionMsg(const S: String);
- begin
- Log('Exception message:');
- LoggedAppMessageBox(PChar(S), PChar(Application.Title), MB_OK or MB_ICONSTOP, True, IDOK);
- end;
- class procedure TMainForm.ShowException(Sender: TObject; E: Exception);
- begin
- ShowExceptionMsg(AddPeriod(E.Message));
- end;
- procedure TMainForm.SetStep(const AStep: TSetupStep; const HandleExceptions: Boolean);
- begin
- CurStep := AStep;
- if CodeRunner <> nil then begin
- try
- CodeRunner.RunProcedures('CurStepChanged', [Ord(CurStep)], False);
- except
- if HandleExceptions then begin
- Log('CurStepChanged raised an exception.');
- Application.HandleException(Self);
- end
- else begin
- Log('CurStepChanged raised an exception (fatal).');
- raise;
- end;
- end;
- end;
- end;
- procedure TerminateApp;
- begin
- { Work around shell32 bug: Don't use PostQuitMessage/Application.Terminate
- here.
- When ShellExecute is called with the name of a folder, it internally
- creates a window used for DDE communication with Windows Explorer. After
- ShellExecute returns, this window eventually receives a posted WM_DDE_ACK
- message back from the DDE server (Windows Explorer), and in response, it
- tries to flush the queue of DDE messages by using a PeekMessage loop.
- Problem is, PeekMessage will return WM_QUIT messages posted with
- PostQuitMessage regardless of the message range specified, and the loop was
- not written with this in mind.
- In previous IS versions, this was causing our WM_QUIT message to be eaten
- if Application.Terminate was called very shortly after a shellexec [Run]
- entry was processed (e.g. if DisableFinishedPage=yes).
- A WM_QUIT message posted with PostMessage instead of PostQuitMessage will
- not be returned by a GetMessage/PeekMessage call with a message range that
- does not include WM_QUIT. }
- PostMessage(0, WM_QUIT, 0, 0);
- end;
- function TMainForm.Install: Boolean;
- procedure ProcessRunEntries;
- var
- CheckIfRestartNeeded: Boolean;
- ChecksumBefore, ChecksumAfter: TSHA256Digest;
- WindowDisabler: TWindowDisabler;
- I: Integer;
- RunEntry: PSetupRunEntry;
- begin
- if Entries[seRun].Count <> 0 then begin
- CheckIfRestartNeeded := (shRestartIfNeededByRun in SetupHeader.Options) and
- not NeedsRestart;
- if CheckIfRestartNeeded then
- ChecksumBefore := MakePendingFileRenameOperationsChecksum;
- var WizardWasHidden := False;
- WindowDisabler := nil;
- try
- for I := 0 to Entries[seRun].Count-1 do begin
- RunEntry := PSetupRunEntry(Entries[seRun][I]);
- if not(roPostInstall in RunEntry.Options) and
- ShouldProcessRunEntry(WizardComponents, WizardTasks, RunEntry) then begin
- { Disable windows during execution of [Run] entries so that a nice
- "beep" is produced if the user tries clicking on WizardForm }
- if WindowDisabler = nil then
- WindowDisabler := TWindowDisabler.Create;
- if RunEntry.StatusMsg <> '' then begin
- try
- WizardForm.StatusLabel.Caption := ExpandConst(RunEntry.StatusMsg);
- except
- { Don't die if the expansion fails with an exception. Just
- display the exception message, and proceed with the default
- status message. }
- Application.HandleException(Self);
- WizardForm.StatusLabel.Caption := SetupMessages[msgStatusRunProgram];
- end;
- end
- else
- WizardForm.StatusLabel.Caption := SetupMessages[msgStatusRunProgram];
- WizardForm.StatusLabel.Update;
- if roHideWizard in RunEntry.Options then begin
- if WizardForm.Visible and not WizardWasHidden then begin
- WizardWasHidden := True;
- WizardForm.Hide;
- end;
- end
- else begin
- if WizardWasHidden then begin
- WizardWasHidden := False;
- WizardForm.Visible := True;
- end;
- end;
- DebugNotifyEntry(seRun, I);
- NotifyBeforeInstallEntry(RunEntry.BeforeInstall);
- ProcessRunEntry(RunEntry);
- NotifyAfterInstallEntry(RunEntry.AfterInstall);
- end;
- end;
- finally
- if WizardWasHidden then
- WizardForm.Visible := True;
- WindowDisabler.Free;
- if CheckIfRestartNeeded then begin
- ChecksumAfter := MakePendingFileRenameOperationsChecksum;
- if not SHA256DigestsEqual(ChecksumBefore, ChecksumAfter) then
- NeedsRestart := True;
- end;
- end;
- if WizardForm.WindowState <> wsMinimized then { VCL bug workaround }
- Application.BringToFront;
- end;
- end;
- procedure RestartApplications;
- const
- ERROR_FAIL_RESTART = 353;
- var
- Error: DWORD;
- WindowDisabler: TWindowDisabler;
- begin
- if not NeedsRestart then begin
- WizardForm.StatusLabel.Caption := SetupMessages[msgStatusRestartingApplications];
- WizardForm.StatusLabel.Update;
- Log('Attempting to restart applications.');
- { Disable windows during application restart so that a nice
- "beep" is produced if the user tries clicking on WizardForm }
- WindowDisabler := TWindowDisabler.Create;
- try
- Error := RmRestart(RmSessionHandle, 0, nil);
- finally
- WindowDisabler.Free;
- end;
- if WizardForm.WindowState <> wsMinimized then { VCL bug workaround }
- Application.BringToFront;
- if Error = ERROR_FAIL_RESTART then
- Log('One or more applications could not be restarted.')
- else if Error <> ERROR_SUCCESS then begin
- RmEndSession(RmSessionHandle);
- RmSessionStarted := False;
- LogFmt('RmRestart returned an error: %d', [Error]);
- end;
- end else
- Log('Need to restart Windows, not attempting to restart applications');
- end;
- var
- Succeeded, ChangesEnvironment, ChangesAssociations: Boolean;
- S: String;
- begin
- Result := False;
- try
- if not WizardForm.ValidateDirEdit then
- Abort;
- WizardDirValue := WizardForm.DirEdit.Text;
- if not WizardForm.ValidateGroupEdit then
- Abort;
- WizardGroupValue := WizardForm.GroupEdit.Text;
- WizardNoIcons := WizardForm.NoIconsCheck.Checked;
- WizardSetupType := WizardForm.GetSetupType();
- WizardForm.GetComponents(WizardComponents, WizardDeselectedComponents);
- WizardForm.GetTasks(WizardTasks, WizardDeselectedTasks);
- WizardPreparingYesRadio := WizardForm.PreparingYesRadio.Checked;
- if InitSaveInf <> '' then
- SaveInf(InitSaveInf);
- Application.Restore;
- if InstallMode = imSilent then
- WizardForm.Visible := True;
- WizardForm.Update;
- SetStep(ssInstall, False);
-
- ChangesEnvironment := EvalDirectiveCheck(SetupHeader.ChangesEnvironment);
- ChangesAssociations := EvalDirectiveCheck(SetupHeader.ChangesAssociations);
- PerformInstall(Succeeded, ChangesEnvironment, ChangesAssociations);
- if not Succeeded then begin
- { The user canceled the install or there was a fatal error }
- TerminateApp;
- Exit;
- end;
- { Can't cancel at any point after PerformInstall, so disable the button }
- WizardForm.CancelButton.Enabled := False;
- ProcessRunEntries;
- if RmDoRestart and
- (InitRestartApplications or
- ((shRestartApplications in SetupHeader.Options) and not InitNoRestartApplications)) then
- RestartApplications;
- SetStep(ssPostInstall, True);
- { Notify Windows of assocations/environment changes *after* ssPostInstall
- since user might set more stuff there }
- if ChangesAssociations then
- SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nil, nil);
- if ChangesEnvironment then
- RefreshEnvironment;
- if InstallMode <> imNormal then
- WizardForm.Hide;
- LogFmt('Need to restart Windows? %s', [SYesNo[NeedsRestart]]);
- if NeedsRestart and not InitNoRestart then begin
- with WizardForm do begin
- ChangeFinishedLabel(ExpandSetupMessage(msgFinishedRestartLabel));
- YesRadio.Visible := True;
- NoRadio.Visible := True;
- end;
- end else begin
- if CreatedIcon then
- S := ExpandSetupMessage(msgFinishedLabel)
- else
- S := ExpandSetupMessage(msgFinishedLabelNoIcons);
- with WizardForm do begin
- ChangeFinishedLabel(S + SNewLine2 + SetupMessages[msgClickFinish]);
- if not NeedsRestart then begin
- UpdateRunList(WizardComponents, WizardTasks);
- RunList.Visible := RunList.Items.Count > 0;
- end;
- end;
- end;
- if InstallMode = imNormal then
- Application.Restore;
- Result := True;
- except
- { If an exception was raised, display the message, then terminate }
- Application.HandleException(Self);
- SetupExitCode := ecNextStepError;
- TerminateApp;
- end;
- end;
- procedure ProcessMessagesProc; far;
- begin
- Application.ProcessMessages;
- end;
- procedure TMainForm.Finish(const FromPreparingPage: Boolean);
- procedure WaitForForegroundLoss;
- function IsForegroundProcess: Boolean;
- var
- W: HWND;
- PID: DWORD;
- begin
- W := GetForegroundWindow;
- Result := False;
- if (W <> 0) and (GetWindowThreadProcessId(W, @PID) <> 0) then
- Result := (PID = GetCurrentProcessId);
- end;
- var
- StartTick: DWORD;
- begin
- StartTick := GetTickCount;
- while IsForegroundProcess do begin
- { Stop if it's taking too long (e.g. if the spawned process never
- displays a window) }
- if Cardinal(GetTickCount - StartTick) >= Cardinal(1000) then
- Break;
- ProcessMessagesProc;
- WaitMessageWithTimeout(10);
- ProcessMessagesProc;
- end;
- end;
- procedure ProcessPostInstallRunEntries;
- var
- WindowDisabler: TWindowDisabler;
- ProcessedNoWait: Boolean;
- I: Integer;
- RunEntry: PSetupRunEntry;
- begin
- WindowDisabler := nil;
- try
- ProcessedNoWait := False;
- with WizardForm do begin
- for I := 0 to RunList.Items.Count-1 do begin
- if RunList.Checked[I] then begin
- { Disable windows before processing the first entry }
- if WindowDisabler = nil then
- WindowDisabler := TWindowDisabler.Create;
- RunEntry := PSetupRunEntry(Entries[seRun][Integer(RunList.ItemObject[I])]);
- DebugNotifyEntry(seRun, Integer(RunList.ItemObject[I]));
- NotifyBeforeInstallEntry(RunEntry.BeforeInstall);
- ProcessRunEntry(RunEntry);
- NotifyAfterInstallEntry(RunEntry.AfterInstall);
- if RunEntry.Wait = rwNoWait then
- ProcessedNoWait := True;
- end;
- end;
- end;
- { Give nowait processes some time to bring themselves to the
- foreground before Setup exits. Without this delay, the application
- underneath Setup can end up coming to the foreground instead.
- (Note: Windows are already disabled at this point.) }
- if ProcessedNoWait then
- WaitForForegroundLoss;
- finally
- WindowDisabler.Free;
- end;
- end;
- var
- S: String;
- begin
- try
- { Deactivate WizardForm so another application doesn't come to the
- foreground when Hide is called. (Needed by WaitForForegroundLoss.) }
- if GetForegroundWindow = WizardForm.Handle then
- SetActiveWindow(Application.Handle);
- WizardForm.Hide;
- if not FromPreparingPage and not NeedsRestart then begin
- ProcessPostInstallRunEntries;
- end else begin
- if FromPreparingPage then
- SetupExitCode := ecPrepareToInstallFailedRestartNeeded
- else if InitRestartExitCode <> 0 then
- SetupExitCode := InitRestartExitCode;
- if InitNoRestart then
- RestartSystem := False
- else begin
- case InstallMode of
- imNormal:
- if FromPreparingPage then
- RestartSystem := WizardForm.PreparingYesRadio.Checked
- else
- RestartSystem := WizardForm.YesRadio.Checked;
- imSilent:
- begin
- if FromPreparingPage then
- S := WizardForm.PrepareToInstallFailureMessage + SNewLine +
- SNewLine + SNewLine + ExpandSetupMessage(msgPrepareToInstallNeedsRestart)
- else
- S := ExpandSetupMessage(msgFinishedRestartMessage);
- RestartSystem :=
- LoggedMsgBox(S, '', mbConfirmation, MB_YESNO, True, IDYES) = IDYES;
- end;
- imVerySilent:
- RestartSystem := True;
- end;
- end;
- if not RestartSystem then
- Log('Will not restart Windows automatically.');
- end;
- SetStep(ssDone, True);
- except
- Application.HandleException(Self);
- SetupExitCode := ecNextStepError;
- end;
- TerminateApp;
- end;
- procedure TMainForm.Close;
- function ConfirmCancel(const DefaultConfirm: Boolean): Boolean;
- var
- Cancel, Confirm: Boolean;
- begin
- Cancel := True;
- Confirm := DefaultConfirm;
- WizardForm.CallCancelButtonClick(Cancel, Confirm);
- Result := Cancel and (not Confirm or ExitSetupMsgBox);
- end;
- begin
- if Assigned(WizardForm) and WizardForm.HandleAllocated and
- IsWindowVisible(WizardForm.Handle) and IsWindowEnabled(WizardForm.Handle) and
- WizardForm.CancelButton.CanFocus then begin
- case CurStep of
- ssPreInstall:
- if ConfirmCancel((WizardForm.CurPageID <> wpPreparing) or (WizardForm.PrepareToInstallFailureMessage = '')) then begin
- if WizardForm.CurPageID = wpPreparing then
- SetupExitCode := ecPrepareToInstallFailed
- else
- SetupExitCode := ecCancelledBeforeInstall;
- TerminateApp;
- end;
- ssInstall:
- if (shAllowCancelDuringInstall in SetupHeader.Options) and not InitNoCancel then
- if ConfirmCancel(True) then
- NeedToAbortInstall := True;
- end;
- end;
- end;
- class procedure TMainForm.AppOnGetActiveFormHandle(var AHandle: HWND);
- begin
- { IDE's TMainForm has this too; see comments there }
- if Application.MainFormOnTaskBar then begin
- AHandle := GetActiveWindow;
- if ((AHandle = 0) or (AHandle = Application.Handle)) and
- Assigned(Application.MainForm) and
- Application.MainForm.HandleAllocated then
- AHandle := GetLastActivePopup(Application.MainFormHandle);
- end;
- end;
- initialization
- Application.OnGetActiveFormHandle := TMainForm.AppOnGetActiveFormHandle;
- end.
|