Setup.MainForm.pas 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. unit Setup.MainForm;
  2. {
  3. Inno Setup
  4. Copyright (C) 1997-2026 Jordan Russell
  5. Portions by Martijn Laan
  6. For conditions of distribution and use, see LICENSE.TXT.
  7. }
  8. interface
  9. uses
  10. Windows, SysUtils, Classes,
  11. Shared.SetupSteps;
  12. type
  13. TMainForm = class(TComponent)
  14. private
  15. class procedure AppOnGetActiveFormHandle(var AHandle: HWND);
  16. public
  17. CurStep: TSetupStep;
  18. destructor Destroy; override;
  19. procedure Close;
  20. procedure Finish(const FromPreparingPage: Boolean);
  21. function Install: Boolean;
  22. procedure SetStep(const AStep: TSetupStep; const HandleExceptions: Boolean);
  23. end;
  24. var
  25. MainForm: TMainForm;
  26. implementation
  27. uses
  28. Messages, ShlObj,
  29. Forms,
  30. SHA256, RestartManager,
  31. Shared.Struct, Shared.CommonFunc, Shared.CommonFunc.Vcl, Shared.SetupMessageIDs,
  32. SetupLdrAndSetup.Messages, Setup.Install,
  33. Setup.MainFunc, Setup.InstFunc, Setup.WizardForm, Setup.LoggingFunc, Shared.SetupTypes;
  34. destructor TMainForm.Destroy;
  35. begin
  36. MainForm := nil; { just to detect use-after-free }
  37. inherited;
  38. end;
  39. procedure TMainForm.SetStep(const AStep: TSetupStep; const HandleExceptions: Boolean);
  40. begin
  41. CurStep := AStep;
  42. if CodeRunner <> nil then begin
  43. try
  44. CodeRunner.RunProcedures('CurStepChanged', [Ord(CurStep)], False);
  45. except
  46. if HandleExceptions then begin
  47. Log('CurStepChanged raised an exception.');
  48. Application.HandleException(Self);
  49. end
  50. else begin
  51. Log('CurStepChanged raised an exception (fatal).');
  52. raise;
  53. end;
  54. end;
  55. end;
  56. end;
  57. procedure TerminateApp;
  58. begin
  59. { Work around shell32 bug: Don't use PostQuitMessage/Application.Terminate
  60. here.
  61. When ShellExecute is called with the name of a folder, it internally
  62. creates a window used for DDE communication with Windows Explorer. After
  63. ShellExecute returns, this window eventually receives a posted WM_DDE_ACK
  64. message back from the DDE server (Windows Explorer), and in response, it
  65. tries to flush the queue of DDE messages by using a PeekMessage loop.
  66. Problem is, PeekMessage will return WM_QUIT messages posted with
  67. PostQuitMessage regardless of the message range specified, and the loop was
  68. not written with this in mind.
  69. In previous IS versions, this was causing our WM_QUIT message to be eaten
  70. if Application.Terminate was called very shortly after a shellexec [Run]
  71. entry was processed (e.g. if DisableFinishedPage=yes).
  72. A WM_QUIT message posted with PostMessage instead of PostQuitMessage will
  73. not be returned by a GetMessage/PeekMessage call with a message range that
  74. does not include WM_QUIT. }
  75. PostMessage(0, WM_QUIT, 0, 0);
  76. end;
  77. function TMainForm.Install: Boolean;
  78. procedure ProcessRunEntries;
  79. var
  80. CheckIfRestartNeeded: Boolean;
  81. ChecksumBefore, ChecksumAfter: TSHA256Digest;
  82. WindowDisabler: TWindowDisabler;
  83. RunEntry: PSetupRunEntry;
  84. begin
  85. if Entries[seRun].Count <> 0 then begin
  86. CheckIfRestartNeeded := (shRestartIfNeededByRun in SetupHeader.Options) and
  87. not NeedsRestart;
  88. if CheckIfRestartNeeded then
  89. ChecksumBefore := MakePendingFileRenameOperationsChecksum;
  90. var WizardWasHidden := False;
  91. WindowDisabler := nil;
  92. try
  93. for var I := 0 to Entries[seRun].Count-1 do begin
  94. RunEntry := PSetupRunEntry(Entries[seRun][I]);
  95. if not(roPostInstall in RunEntry.Options) and
  96. ShouldProcessRunEntry(WizardComponents, WizardTasks, RunEntry) then begin
  97. { Disable windows during execution of [Run] entries so that a nice
  98. "beep" is produced if the user tries clicking on WizardForm }
  99. if WindowDisabler = nil then
  100. WindowDisabler := TWindowDisabler.Create;
  101. if RunEntry.StatusMsg <> '' then begin
  102. try
  103. WizardForm.StatusLabel.Caption := ExpandConst(RunEntry.StatusMsg);
  104. except
  105. { Don't die if the expansion fails with an exception. Just
  106. display the exception message, and proceed with the default
  107. status message. }
  108. Application.HandleException(Self);
  109. WizardForm.StatusLabel.Caption := SetupMessages[msgStatusRunProgram];
  110. end;
  111. end
  112. else
  113. WizardForm.StatusLabel.Caption := SetupMessages[msgStatusRunProgram];
  114. WizardForm.StatusLabel.Update;
  115. if roHideWizard in RunEntry.Options then begin
  116. if WizardForm.Visible and not WizardWasHidden then begin
  117. WizardWasHidden := True;
  118. WizardForm.Hide;
  119. end;
  120. end
  121. else begin
  122. if WizardWasHidden then begin
  123. WizardWasHidden := False;
  124. WizardForm.Visible := True;
  125. end;
  126. end;
  127. DebugNotifyEntry(seRun, I);
  128. NotifyBeforeInstallEntry(RunEntry.BeforeInstall);
  129. ProcessRunEntry(RunEntry);
  130. NotifyAfterInstallEntry(RunEntry.AfterInstall);
  131. end;
  132. end;
  133. finally
  134. if WizardWasHidden then
  135. WizardForm.Visible := True;
  136. WindowDisabler.Free;
  137. if CheckIfRestartNeeded then begin
  138. ChecksumAfter := MakePendingFileRenameOperationsChecksum;
  139. if not SHA256DigestsEqual(ChecksumBefore, ChecksumAfter) then
  140. NeedsRestart := True;
  141. end;
  142. end;
  143. if WizardForm.WindowState <> wsMinimized then { VCL bug workaround }
  144. Application.BringToFront;
  145. end;
  146. end;
  147. procedure RestartApplications;
  148. const
  149. ERROR_FAIL_RESTART = 353;
  150. var
  151. Error: DWORD;
  152. WindowDisabler: TWindowDisabler;
  153. begin
  154. if not NeedsRestart then begin
  155. WizardForm.StatusLabel.Caption := SetupMessages[msgStatusRestartingApplications];
  156. WizardForm.StatusLabel.Update;
  157. Log('Attempting to restart applications.');
  158. { Disable windows during application restart so that a nice
  159. "beep" is produced if the user tries clicking on WizardForm }
  160. WindowDisabler := TWindowDisabler.Create;
  161. try
  162. Error := RmRestart(RmSessionHandle, 0, nil);
  163. finally
  164. WindowDisabler.Free;
  165. end;
  166. if WizardForm.WindowState <> wsMinimized then { VCL bug workaround }
  167. Application.BringToFront;
  168. if Error = ERROR_FAIL_RESTART then
  169. Log('One or more applications could not be restarted.')
  170. else if Error <> ERROR_SUCCESS then begin
  171. RmEndSession(RmSessionHandle);
  172. RmSessionStarted := False;
  173. LogFmt('RmRestart returned an error: %d', [Error]);
  174. end;
  175. end else
  176. Log('Need to restart Windows, not attempting to restart applications');
  177. end;
  178. var
  179. Succeeded, ChangesEnvironment, ChangesAssociations: Boolean;
  180. S: String;
  181. begin
  182. Result := False;
  183. try
  184. if not WizardForm.ValidateDirEdit then
  185. Abort;
  186. WizardDirValue := WizardForm.DirEdit.Text;
  187. if not WizardForm.ValidateGroupEdit then
  188. Abort;
  189. WizardGroupValue := WizardForm.GroupEdit.Text;
  190. WizardNoIcons := WizardForm.NoIconsCheck.Checked;
  191. WizardSetupType := WizardForm.GetSetupType();
  192. WizardForm.GetComponents(WizardComponents, WizardDeselectedComponents);
  193. WizardForm.GetTasks(WizardTasks, WizardDeselectedTasks);
  194. WizardPreparingYesRadio := WizardForm.PreparingYesRadio.Checked;
  195. if InitSaveInf <> '' then
  196. SaveInf(InitSaveInf);
  197. Application.Restore;
  198. if InstallMode = imSilent then
  199. WizardForm.Visible := True;
  200. WizardForm.Update;
  201. SetStep(ssInstall, False);
  202. ChangesEnvironment := EvalDirectiveCheck(SetupHeader.ChangesEnvironment);
  203. ChangesAssociations := EvalDirectiveCheck(SetupHeader.ChangesAssociations);
  204. PerformInstall(Succeeded, ChangesEnvironment, ChangesAssociations);
  205. if not Succeeded then begin
  206. { The user canceled the install or there was a fatal error }
  207. TerminateApp;
  208. Exit;
  209. end;
  210. { Can't cancel at any point after PerformInstall, so disable the button }
  211. WizardForm.CancelButton.Enabled := False;
  212. ProcessRunEntries;
  213. if RmDoRestart and
  214. (InitRestartApplications or
  215. ((shRestartApplications in SetupHeader.Options) and not InitNoRestartApplications)) then
  216. RestartApplications;
  217. SetStep(ssPostInstall, True);
  218. { Notify Windows of assocations/environment changes *after* ssPostInstall
  219. since user might set more stuff there }
  220. if ChangesAssociations then
  221. SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nil, nil);
  222. if ChangesEnvironment then
  223. RefreshEnvironment;
  224. if InstallMode <> imNormal then
  225. WizardForm.Hide;
  226. LogFmt('Need to restart Windows? %s', [SYesNo[NeedsRestart]]);
  227. if NeedsRestart and not InitNoRestart then begin
  228. with WizardForm do begin
  229. ChangeFinishedLabel(ExpandSetupMessage(msgFinishedRestartLabel));
  230. YesRadio.Visible := True;
  231. NoRadio.Visible := True;
  232. end;
  233. end else begin
  234. if CreatedIcon then
  235. S := ExpandSetupMessage(msgFinishedLabel)
  236. else
  237. S := ExpandSetupMessage(msgFinishedLabelNoIcons);
  238. with WizardForm do begin
  239. ChangeFinishedLabel(S + SNewLine2 + SetupMessages[msgClickFinish]);
  240. if not NeedsRestart then begin
  241. UpdateRunList(WizardComponents, WizardTasks);
  242. RunList.Visible := RunList.Items.Count > 0;
  243. end;
  244. end;
  245. end;
  246. if InstallMode = imNormal then
  247. Application.Restore;
  248. Result := True;
  249. except
  250. { If an exception was raised, display the message, then terminate }
  251. Application.HandleException(Self);
  252. SetupExitCode := ecNextStepError;
  253. TerminateApp;
  254. end;
  255. end;
  256. procedure ProcessMessagesProc; far;
  257. begin
  258. Application.ProcessMessages;
  259. end;
  260. procedure TMainForm.Finish(const FromPreparingPage: Boolean);
  261. procedure WaitForForegroundLoss;
  262. function IsForegroundProcess: Boolean;
  263. var
  264. W: HWND;
  265. PID: DWORD;
  266. begin
  267. W := GetForegroundWindow;
  268. Result := False;
  269. if (W <> 0) and (GetWindowThreadProcessId(W, @PID) <> 0) then
  270. Result := (PID = GetCurrentProcessId);
  271. end;
  272. var
  273. StartTick: DWORD;
  274. begin
  275. StartTick := GetTickCount;
  276. while IsForegroundProcess do begin
  277. { Stop if it's taking too long (e.g. if the spawned process never
  278. displays a window) }
  279. if Cardinal(GetTickCount - StartTick) >= Cardinal(1000) then
  280. Break;
  281. ProcessMessagesProc;
  282. WaitMessageWithTimeout(10);
  283. ProcessMessagesProc;
  284. end;
  285. end;
  286. procedure ProcessPostInstallRunEntries;
  287. var
  288. WindowDisabler: TWindowDisabler;
  289. ProcessedNoWait: Boolean;
  290. I: Integer;
  291. RunEntry: PSetupRunEntry;
  292. begin
  293. WindowDisabler := nil;
  294. try
  295. ProcessedNoWait := False;
  296. with WizardForm do begin
  297. for I := 0 to RunList.Items.Count-1 do begin
  298. if RunList.Checked[I] then begin
  299. { Disable windows before processing the first entry }
  300. if WindowDisabler = nil then
  301. WindowDisabler := TWindowDisabler.Create;
  302. RunEntry := PSetupRunEntry(Entries[seRun][Integer(RunList.ItemObject[I])]);
  303. DebugNotifyEntry(seRun, Integer(RunList.ItemObject[I]));
  304. NotifyBeforeInstallEntry(RunEntry.BeforeInstall);
  305. ProcessRunEntry(RunEntry);
  306. NotifyAfterInstallEntry(RunEntry.AfterInstall);
  307. if RunEntry.Wait = rwNoWait then
  308. ProcessedNoWait := True;
  309. end;
  310. end;
  311. end;
  312. { Give nowait processes some time to bring themselves to the
  313. foreground before Setup exits. Without this delay, the application
  314. underneath Setup can end up coming to the foreground instead.
  315. (Note: Windows are already disabled at this point.) }
  316. if ProcessedNoWait then
  317. WaitForForegroundLoss;
  318. finally
  319. WindowDisabler.Free;
  320. end;
  321. end;
  322. var
  323. S: String;
  324. begin
  325. try
  326. { Deactivate WizardForm so another application doesn't come to the
  327. foreground when Hide is called. (Needed by WaitForForegroundLoss.) }
  328. if GetForegroundWindow = WizardForm.Handle then
  329. SetActiveWindow(Application.Handle);
  330. WizardForm.Hide;
  331. if not FromPreparingPage and not NeedsRestart then begin
  332. ProcessPostInstallRunEntries;
  333. end else begin
  334. if FromPreparingPage then
  335. SetupExitCode := ecPrepareToInstallFailedRestartNeeded
  336. else if InitRestartExitCode <> 0 then
  337. SetupExitCode := InitRestartExitCode;
  338. if InitNoRestart then
  339. RestartSystem := False
  340. else begin
  341. case InstallMode of
  342. imNormal:
  343. if FromPreparingPage then
  344. RestartSystem := WizardForm.PreparingYesRadio.Checked
  345. else
  346. RestartSystem := WizardForm.YesRadio.Checked;
  347. imSilent:
  348. begin
  349. if FromPreparingPage then
  350. S := WizardForm.PrepareToInstallFailureMessage + SNewLine +
  351. SNewLine + SNewLine + ExpandSetupMessage(msgPrepareToInstallNeedsRestart)
  352. else
  353. S := ExpandSetupMessage(msgFinishedRestartMessage);
  354. RestartSystem :=
  355. LoggedMsgBox(S, '', mbConfirmation, MB_YESNO, True, IDYES) = IDYES;
  356. end;
  357. imVerySilent:
  358. RestartSystem := True;
  359. end;
  360. end;
  361. if not RestartSystem then
  362. Log('Will not restart Windows automatically.');
  363. end;
  364. SetStep(ssDone, True);
  365. except
  366. Application.HandleException(Self);
  367. SetupExitCode := ecNextStepError;
  368. end;
  369. TerminateApp;
  370. end;
  371. procedure TMainForm.Close;
  372. function ConfirmCancel(const DefaultConfirm: Boolean): Boolean;
  373. var
  374. Cancel, Confirm: Boolean;
  375. begin
  376. Cancel := True;
  377. Confirm := DefaultConfirm;
  378. WizardForm.CallCancelButtonClick(Cancel, Confirm);
  379. Result := Cancel and (not Confirm or ExitSetupMsgBox);
  380. end;
  381. begin
  382. if Assigned(WizardForm) and WizardForm.HandleAllocated and
  383. IsWindowVisible(WizardForm.Handle) and IsWindowEnabled(WizardForm.Handle) and
  384. WizardForm.CancelButton.CanFocus then begin
  385. case CurStep of
  386. ssPreInstall:
  387. if ConfirmCancel((WizardForm.CurPageID <> wpPreparing) or (WizardForm.PrepareToInstallFailureMessage = '')) then begin
  388. if WizardForm.CurPageID = wpPreparing then
  389. SetupExitCode := ecPrepareToInstallFailed
  390. else
  391. SetupExitCode := ecCancelledBeforeInstall;
  392. TerminateApp;
  393. end;
  394. ssInstall:
  395. if (shAllowCancelDuringInstall in SetupHeader.Options) and not InitNoCancel then
  396. if ConfirmCancel(True) then
  397. NeedToAbortInstall := True;
  398. end;
  399. end;
  400. end;
  401. class procedure TMainForm.AppOnGetActiveFormHandle(var AHandle: HWND);
  402. begin
  403. { IDE's TMainForm has this too; see comments there }
  404. if Application.MainFormOnTaskBar then begin
  405. AHandle := GetActiveWindow;
  406. if ((AHandle = 0) or (AHandle = Application.Handle)) and
  407. Assigned(Application.MainForm) and
  408. Application.MainForm.HandleAllocated then
  409. AHandle := GetLastActivePopup(Application.MainFormHandle);
  410. end;
  411. end;
  412. initialization
  413. Application.OnGetActiveFormHandle := TMainForm.AppOnGetActiveFormHandle;
  414. end.