fix(startup): hide the taskbar proxy from a worker thread

Why every previous attempt could not work: the main thread is BUSY inside
FormCreate and pumps no message, and that is exactly when FMX creates and
shows its taskbar proxy. A WinEvent hook, a timer, a queued call — none of
them can run in that window. Deferring the server start shortened the window
but did not remove it, because the proxy is created regardless.

A worker thread is the one thing still running while the main thread blocks.
It polls for our TFMAppClass window (matched on our own process id) and hides
it the instant it appears; ShowWindow is safe cross-thread. It stops as soon
as the tray startup completes, with a 10 s ceiling so it can never linger.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
r-zakarya
2026-07-27 12:37:51 +01:00
parent 17e48aa688
commit 4f2aff9de4
+41
View File
@@ -227,6 +227,7 @@ procedure WinStateEventProc(hWinEventHook: THandle; AEvent: DWORD; AWnd: HWND;
idObject, idChild: LongInt; idEventThread, dwmsEventTime: DWORD); stdcall; forward; idObject, idChild: LongInt; idEventThread, dwmsEventTime: DWORD); stdcall; forward;
procedure TrayShowEventProc(hWinEventHook: THandle; AEvent: DWORD; AWnd: HWND; procedure TrayShowEventProc(hWinEventHook: THandle; AEvent: DWORD; AWnd: HWND;
idObject, idChild: LongInt; idEventThread, dwmsEventTime: DWORD); stdcall; forward; idObject, idChild: LongInt; idEventThread, dwmsEventTime: DWORD); stdcall; forward;
function FindOurFMXAppWindow: HWND; forward;
procedure TMainForm.FormCreate(Sender: TObject); procedure TMainForm.FormCreate(Sender: TObject);
begin begin
@@ -247,6 +248,29 @@ begin
@TrayShowEventProc, GetCurrentProcessId, 0, WINEVENT_INCONTEXT); @TrayShowEventProc, GetCurrentProcessId, 0, WINEVENT_INCONTEXT);
LogLine('[tray] FormCreate begin at ' + FormatDateTime('hh:nn:ss.zzz', Now) + LogLine('[tray] FormCreate begin at ' + FormatDateTime('hh:nn:ss.zzz', Now) +
' show-hook=' + BoolToStr(FShowEventHook <> 0, True)); ' show-hook=' + BoolToStr(FShowEventHook <> 0, True));
// The decisive piece. Everything else runs on the main thread, which is
// BUSY inside FormCreate and pumps no message — so no hook, timer or
// queued call can fire while the FMX taskbar proxy is created and shown.
// A worker thread is the only thing still running then. It polls for the
// proxy and hides it the moment it appears; ShowWindow is safe to call
// cross-thread. Stops on GTrayStartupPending or after 10 s, whichever
// comes first, so it can never linger.
TThread.CreateAnonymousThread(
procedure
var
LWnd: HWND;
LTicks: Integer;
begin
LTicks := 0;
while GTrayStartupPending and (LTicks < 1000) do
begin
LWnd := FindOurFMXAppWindow;
if (LWnd <> 0) and IsWindowVisible(LWnd) then
ShowWindow(LWnd, SW_HIDE);
Sleep(10);
Inc(LTicks);
end;
end).Start;
TrayHideNow('hide#1 top-of-FormCreate'); TrayHideNow('hide#1 top-of-FormCreate');
end; end;
@@ -1659,6 +1683,23 @@ end;
// process shows gets hidden again immediately. Scoped to our process by the // process shows gets hidden again immediately. Scoped to our process by the
// hook itself; WebView2 runs in separate processes so its windows never reach // hook itself; WebView2 runs in separate processes so its windows never reach
// here. Torn down as soon as startup finishes. // here. Torn down as soon as startup finishes.
// Our FMX taskbar proxy (TFMAppClass), found by class name and filtered on our
// own process id — the same lookup TPMBridge does, duplicated here because it
// must work from a worker thread before FBridge even exists.
function FindOurFMXAppWindow: HWND;
var
LPid: DWORD;
begin
Result := FindWindowEx(0, 0, 'TFMAppClass', nil);
while Result <> 0 do
begin
LPid := 0;
GetWindowThreadProcessId(Result, LPid);
if LPid = GetCurrentProcessId then Exit;
Result := FindWindowEx(0, Result, 'TFMAppClass', nil);
end;
end;
procedure TrayShowEventProc(hWinEventHook: THandle; AEvent: DWORD; AWnd: HWND; procedure TrayShowEventProc(hWinEventHook: THandle; AEvent: DWORD; AWnd: HWND;
idObject, idChild: LongInt; idEventThread, dwmsEventTime: DWORD); stdcall; idObject, idChild: LongInt; idEventThread, dwmsEventTime: DWORD); stdcall;
begin begin