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