From eec7bc4f8051590b5b98c019cf5797db6cc5a030 Mon Sep 17 00:00:00 2001 From: r-zakarya <82443831+r-zakarya@users.noreply.github.com> Date: Mon, 27 Jul 2026 05:42:04 +0100 Subject: [PATCH] fix(startup): revert the .dpr hide (AV) and hide on first activate instead MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- delphi-backend/PMServer.dpr | 16 ---------------- delphi-backend/UMainForm.pas | 33 +++++++++++++++++++++++++-------- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/delphi-backend/PMServer.dpr b/delphi-backend/PMServer.dpr index 9b57c3a..b1b5730 100644 --- a/delphi-backend/PMServer.dpr +++ b/delphi-backend/PMServer.dpr @@ -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. diff --git a/delphi-backend/UMainForm.pas b/delphi-backend/UMainForm.pas index 62a2c86..2929977 100644 --- a/delphi-backend/UMainForm.pas +++ b/delphi-backend/UMainForm.pas @@ -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