fix(startup): hide the FMX taskbar proxy the instant it is created
The instrumented run pinned it down: the FORM is never visible (form_visible_was=False at all four hide points). What shows is the FMX taskbar proxy — app=0 at 20.145, app=854682 at 20.974 — so it is created partway through FormCreate and stayed on screen for the best part of a second until the first hide that could see it. A WinEvent hook on EVENT_OBJECT_SHOW, active only for the --tray startup, hides any top-level window of ours the moment Windows shows it, and is torn down once MinimizeToTray has run. Two details that would each have broken it: - WINEVENT_INCONTEXT, not OUTOFCONTEXT: the latter queues the callback until the thread pumps messages, which does not happen during FormCreate — exactly when the proxy appears. - The pending flag is a unit-level var, not a form field: CreateForm assigns the MainForm global only after the constructor returns, so a callback firing during FormCreate could not have reached the form. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -84,6 +84,11 @@ type
|
|||||||
// restore's resize is indistinguishable from a user gesture by placement
|
// restore's resize is indistinguishable from a user gesture by placement
|
||||||
// flags or foreground ownership — both were measured and rejected).
|
// flags or foreground ownership — both were measured and rejected).
|
||||||
FWinEventHook: THandle;
|
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
|
// True from "minimise starts" until just after the restore settles, so the
|
||||||
// resizes FMX fires while restoring are not recorded as user intent.
|
// resizes FMX fires while restoring are not recorded as user intent.
|
||||||
FIgnoreResizes: Boolean;
|
FIgnoreResizes: Boolean;
|
||||||
@@ -195,6 +200,11 @@ function MaskAccessToken(const AUrl: string): string; forward;
|
|||||||
|
|
||||||
// ---- WinEvent hook plumbing (see the maximised-state block further down) --
|
// ---- WinEvent hook plumbing (see the maximised-state block further down) --
|
||||||
const
|
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_MINIMIZESTART = $0016;
|
||||||
EVENT_SYSTEM_MINIMIZEEND = $0017;
|
EVENT_SYSTEM_MINIMIZEEND = $0017;
|
||||||
WINEVENT_OUTOFCONTEXT = $0000;
|
WINEVENT_OUTOFCONTEXT = $0000;
|
||||||
@@ -207,8 +217,16 @@ function UnhookWinEvent(hWinEventHook: THandle): BOOL;
|
|||||||
stdcall; external 'user32.dll' name 'UnhookWinEvent';
|
stdcall; external 'user32.dll' name 'UnhookWinEvent';
|
||||||
|
|
||||||
// Defined lower down, next to the window-state logic it serves.
|
// 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;
|
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;
|
||||||
|
idObject, idChild: LongInt; idEventThread, dwmsEventTime: DWORD); stdcall; forward;
|
||||||
|
|
||||||
procedure TMainForm.FormCreate(Sender: TObject);
|
procedure TMainForm.FormCreate(Sender: TObject);
|
||||||
begin
|
begin
|
||||||
@@ -223,7 +241,12 @@ begin
|
|||||||
begin
|
begin
|
||||||
Visible := False;
|
Visible := False;
|
||||||
OnActivate := TrayStartupHideOnce;
|
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');
|
TrayHideNow('hide#1 top-of-FormCreate');
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -338,6 +361,12 @@ begin
|
|||||||
procedure
|
procedure
|
||||||
begin
|
begin
|
||||||
FBridge.MinimizeToTray;
|
FBridge.MinimizeToTray;
|
||||||
|
GTrayStartupPending := False;
|
||||||
|
if FShowEventHook <> 0 then
|
||||||
|
begin
|
||||||
|
UnhookWinEvent(FShowEventHook);
|
||||||
|
FShowEventHook := 0;
|
||||||
|
end;
|
||||||
LogLine('[tray] deferred MinimizeToTray ran at ' +
|
LogLine('[tray] deferred MinimizeToTray ran at ' +
|
||||||
FormatDateTime('hh:nn:ss.zzz', Now));
|
FormatDateTime('hh:nn:ss.zzz', Now));
|
||||||
end);
|
end);
|
||||||
@@ -351,6 +380,11 @@ begin
|
|||||||
UnhookWinEvent(FWinEventHook);
|
UnhookWinEvent(FWinEventHook);
|
||||||
FWinEventHook := 0;
|
FWinEventHook := 0;
|
||||||
end;
|
end;
|
||||||
|
if FShowEventHook <> 0 then
|
||||||
|
begin
|
||||||
|
UnhookWinEvent(FShowEventHook);
|
||||||
|
FShowEventHook := 0;
|
||||||
|
end;
|
||||||
if Assigned(FFileSaveChunks) then
|
if Assigned(FFileSaveChunks) then
|
||||||
begin
|
begin
|
||||||
for var LSB in FFileSaveChunks.Values do LSB.Free;
|
for var LSB in FFileSaveChunks.Values do LSB.Free;
|
||||||
@@ -1608,6 +1642,19 @@ begin
|
|||||||
else if AEvent = EVENT_SYSTEM_MINIMIZEEND then MainForm.WinStateMinimizeEnd;
|
else if AEvent = EVENT_SYSTEM_MINIMIZEEND then MainForm.WinStateMinimizeEnd;
|
||||||
end;
|
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);
|
procedure TMainForm.TrayHideNow(const ATag: string);
|
||||||
var
|
var
|
||||||
LHwnd, LAppHwnd: HWND;
|
LHwnd, LAppHwnd: HWND;
|
||||||
|
|||||||
Reference in New Issue
Block a user