fix(startup): revert the .dpr hide (AV) and hide on first activate instead
The .dpr version crashed with an EAccessViolation writing to 000000CE — touching MainForm.Visible/Handle between CreateForm and Run is evidently too early for FMX. Reverted; the app builds and runs as before. Replaced with a one-shot OnActivate handler installed in FormCreate only for --tray: it fires the instant FMX shows the form, hides it (FMX + Win32) and unhooks itself, so it can never fight the user later. That runs far earlier than the deferred MinimizeToTray, which waits for the message loop and hence for the DB open, server start and WebView2 init — the second the window used to spend on screen. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,8 @@
|
||||
program PMServer;
|
||||
|
||||
uses
|
||||
Winapi.Windows,
|
||||
System.SysUtils,
|
||||
System.StartUpCopy,
|
||||
FMX.Forms,
|
||||
FMX.Platform.Win,
|
||||
PM.SingleInstance in 'Source\PM.SingleInstance.pas',
|
||||
UMainForm in 'UMainForm.pas' {MainForm},
|
||||
PM.JSON in 'Source\PM.JSON.pas',
|
||||
@@ -47,18 +44,5 @@ begin
|
||||
|
||||
Application.Initialize;
|
||||
Application.CreateForm(TMainForm, MainForm);
|
||||
// --tray ("Start with Windows"): keep the window off screen entirely.
|
||||
// FormCreate already sets Visible := False, but FMX re-shows the main form
|
||||
// after the constructor returns, which is why the window still flashed for
|
||||
// ~1 s during the Windows logon. Doing it HERE — after CreateForm, before
|
||||
// Run pumps any message — is the point where it sticks. The handle still
|
||||
// exists, so WebView2 initialises normally and the vault is ready the
|
||||
// moment the user clicks the tray icon.
|
||||
if FindCmdLineSwitch('tray', True) then
|
||||
begin
|
||||
MainForm.Visible := False;
|
||||
if MainForm.Handle <> nil then
|
||||
ShowWindow(WindowHandleToPlatform(MainForm.Handle).Wnd, SW_HIDE);
|
||||
end;
|
||||
Application.Run;
|
||||
end.
|
||||
|
||||
@@ -171,6 +171,9 @@ type
|
||||
// our own message queue, so these already run on the main thread.
|
||||
// Every tray restore goes through here so the minimise guard cannot get
|
||||
// stuck raised (see the implementation).
|
||||
// One-shot: hides the window the first time FMX shows it on a --tray
|
||||
// launch, then unhooks itself.
|
||||
procedure TrayStartupHideOnce(Sender: TObject);
|
||||
procedure RestoreWindowFromTray;
|
||||
procedure WinStateMinimizeStart;
|
||||
procedure WinStateMinimizeEnd;
|
||||
@@ -206,15 +209,18 @@ procedure WinStateEventProc(hWinEventHook: THandle; AEvent: DWORD; AWnd: HWND;
|
||||
|
||||
procedure TMainForm.FormCreate(Sender: TObject);
|
||||
begin
|
||||
// --tray (the "Start with Windows" registry entry): never let the window
|
||||
// become visible at all. Hiding it later — even via ForceQueue — only runs
|
||||
// once the message loop is free, i.e. AFTER the DB open, the HTTP server
|
||||
// start and the WebView2 init have finished, so the window sat on screen
|
||||
// for a second or two during the Windows logon. Setting Visible here, before
|
||||
// any of that, means there is nothing to flash. MinimizeToTray still runs
|
||||
// below to add the tray icon and drop the taskbar entry.
|
||||
// --tray ("Start with Windows"): get the window off screen as early as
|
||||
// possible. Visible := False alone is not enough — FMX re-shows the main
|
||||
// form after the constructor returns — so OnActivate hides it again the
|
||||
// instant it appears. That fires on the very first show, long before the
|
||||
// deferred MinimizeToTray below, which only runs once the message loop is
|
||||
// free (i.e. after the DB open, the server start and the WebView2 init:
|
||||
// the second the window used to sit on screen during the Windows logon).
|
||||
if FindCmdLineSwitch('tray', True) then
|
||||
Visible := False;
|
||||
begin
|
||||
Visible := False;
|
||||
OnActivate := TrayStartupHideOnce;
|
||||
end;
|
||||
|
||||
// The .fmx ships WindowState = wsMaximized, so that is the starting intent.
|
||||
// Wired here (not in the .fmx) to keep the designer file untouched.
|
||||
@@ -1592,6 +1598,17 @@ begin
|
||||
else if AEvent = EVENT_SYSTEM_MINIMIZEEND then MainForm.WinStateMinimizeEnd;
|
||||
end;
|
||||
|
||||
procedure TMainForm.TrayStartupHideOnce(Sender: TObject);
|
||||
var
|
||||
LHwnd: HWND;
|
||||
begin
|
||||
OnActivate := nil; // one shot — never fight the user afterwards
|
||||
Visible := False;
|
||||
LHwnd := WindowHandleToPlatform(Self.Handle).Wnd;
|
||||
if LHwnd <> 0 then
|
||||
ShowWindow(LHwnd, SW_HIDE);
|
||||
end;
|
||||
|
||||
// Single entry point for every tray restore. ExecuteAutofill really minimises
|
||||
// the window (SW_MINIMIZE) before typing, which raises the guard; if the window
|
||||
// is then HIDDEN to the tray instead of being un-minimised, MINIMIZEEND may
|
||||
|
||||
Reference in New Issue
Block a user