diff --git a/delphi-backend/UMainForm.pas b/delphi-backend/UMainForm.pas index d6ce83a..cabb1f5 100644 --- a/delphi-backend/UMainForm.pas +++ b/delphi-backend/UMainForm.pas @@ -84,6 +84,11 @@ type // restore's resize is indistinguishable from a user gesture by placement // flags or foreground ownership — both were measured and rejected). FWinEventHook: THandle; + // --tray only: catches EVENT_OBJECT_SHOW so any top-level window of ours + // is hidden the instant Windows shows it. The FMX taskbar proxy is created + // partway through FormCreate — it does not exist at the first hide points + // and was therefore visible for the best part of a second. + FShowEventHook: THandle; // True from "minimise starts" until just after the restore settles, so the // resizes FMX fires while restoring are not recorded as user intent. FIgnoreResizes: Boolean; @@ -195,6 +200,11 @@ function MaskAccessToken(const AUrl: string): string; forward; // ---- WinEvent hook plumbing (see the maximised-state block further down) -- const + EVENT_OBJECT_SHOW = $8002; + // Synchronous delivery, in our own process. OUTOFCONTEXT would queue the + // callback until the thread pumps messages — which does not happen during + // FormCreate, exactly when we need to react. + WINEVENT_INCONTEXT = $0004; EVENT_SYSTEM_MINIMIZESTART = $0016; EVENT_SYSTEM_MINIMIZEEND = $0017; WINEVENT_OUTOFCONTEXT = $0000; @@ -207,8 +217,16 @@ function UnhookWinEvent(hWinEventHook: THandle): BOOL; stdcall; external 'user32.dll' name 'UnhookWinEvent'; // Defined lower down, next to the window-state logic it serves. +// Unit-level, NOT a form field: Application.CreateForm only assigns MainForm +// after the constructor returns, so during FormCreate the global is still nil +// and the callback could not reach the form at all. +var + GTrayStartupPending: Boolean = False; + procedure WinStateEventProc(hWinEventHook: THandle; AEvent: DWORD; AWnd: HWND; idObject, idChild: LongInt; idEventThread, dwmsEventTime: DWORD); stdcall; forward; +procedure TrayShowEventProc(hWinEventHook: THandle; AEvent: DWORD; AWnd: HWND; + idObject, idChild: LongInt; idEventThread, dwmsEventTime: DWORD); stdcall; forward; procedure TMainForm.FormCreate(Sender: TObject); begin @@ -223,7 +241,12 @@ begin begin Visible := False; OnActivate := TrayStartupHideOnce; - LogLine('[tray] FormCreate begin at ' + FormatDateTime('hh:nn:ss.zzz', Now)); + GTrayStartupPending := True; + FShowEventHook := SetWinEventHook( + EVENT_OBJECT_SHOW, EVENT_OBJECT_SHOW, 0, + @TrayShowEventProc, GetCurrentProcessId, 0, WINEVENT_INCONTEXT); + LogLine('[tray] FormCreate begin at ' + FormatDateTime('hh:nn:ss.zzz', Now) + + ' show-hook=' + BoolToStr(FShowEventHook <> 0, True)); TrayHideNow('hide#1 top-of-FormCreate'); end; @@ -338,6 +361,12 @@ begin procedure begin FBridge.MinimizeToTray; + GTrayStartupPending := False; + if FShowEventHook <> 0 then + begin + UnhookWinEvent(FShowEventHook); + FShowEventHook := 0; + end; LogLine('[tray] deferred MinimizeToTray ran at ' + FormatDateTime('hh:nn:ss.zzz', Now)); end); @@ -351,6 +380,11 @@ begin UnhookWinEvent(FWinEventHook); FWinEventHook := 0; end; + if FShowEventHook <> 0 then + begin + UnhookWinEvent(FShowEventHook); + FShowEventHook := 0; + end; if Assigned(FFileSaveChunks) then begin for var LSB in FFileSaveChunks.Values do LSB.Free; @@ -1608,6 +1642,19 @@ begin else if AEvent = EVENT_SYSTEM_MINIMIZEEND then MainForm.WinStateMinimizeEnd; end; +// While a --tray launch is still starting up, every top-level window our +// process shows gets hidden again immediately. Scoped to our process by the +// hook itself; WebView2 runs in separate processes so its windows never reach +// here. Torn down as soon as startup finishes. +procedure TrayShowEventProc(hWinEventHook: THandle; AEvent: DWORD; AWnd: HWND; + idObject, idChild: LongInt; idEventThread, dwmsEventTime: DWORD); stdcall; +begin + if not GTrayStartupPending then Exit; + if (idObject <> OBJID_WINDOW) or (AWnd = 0) then Exit; + if GetParent(AWnd) <> 0 then Exit; // top-level windows only + ShowWindow(AWnd, SW_HIDE); +end; + procedure TMainForm.TrayHideNow(const ATag: string); var LHwnd, LAppHwnd: HWND;