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:
r-zakarya
2026-07-27 05:42:04 +01:00
parent 169cdddf9d
commit eec7bc4f80
2 changed files with 25 additions and 24 deletions
+25 -8
View File
@@ -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