fix(window): use foreground ownership to tell a user resize from a restore

The log settled the previous attempt: WPF_RESTORETOMAXIMIZED (flags=2) is set
for ANY maximised window, not just one on its way back from minimised, and
showCmd is already 1/3 by the time the resize fires. Testing those classified
the user's own Maximize click as a restore, so the intent was never recorded
(want stayed False) and nothing could be re-applied afterwards.

Windows exposes no restore marker here. What does separate the two: a user
gesture happens while the window OWNS THE FOREGROUND; FMX's re-layout during a
restore (and during the startup burst) runs before it does. Resizes that
arrive without foreground ownership are no longer recorded as intent, and
re-apply a maximised state the user had left.

Placement is still read, for the log only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
r-zakarya
2026-07-25 22:42:05 +01:00
parent 03c638b2e1
commit ca54c39c75
+17 -14
View File
@@ -1520,24 +1520,27 @@ begin
if LHwnd = 0 then Exit;
if IsIconic(LHwnd) then Exit; // nothing to learn while minimised
// OnActivate turned out to be useless as a "the window came back" signal —
// it also fires on plain focus churn with the WebView2 child, and even right
// after the user clicks Maximize. Ask Windows instead: WPF_RESTORETOMAXIMIZED
// means "this window is on its way back from minimised and belongs
// maximised", which is exactly the resize we must not mistake for the user
// un-maximising.
FillChar(LPlace, SizeOf(LPlace), 0);
// Windows offers no usable "this resize is a restore" signal: the log showed
// WPF_RESTORETOMAXIMIZED (flags=2) is set for ANY maximised window, and
// showCmd is already 1/3 by then — testing those classified the user's own
// Maximize click as a restore and dropped the intent.
//
// What does separate them: a user gesture happens while the window is the
// FOREGROUND window. FMX's re-layout during a restore (and during startup)
// runs before the window owns the foreground.
LIsRestore := GetForegroundWindow <> LHwnd;
FillChar(LPlace, SizeOf(LPlace), 0); // diagnostics only
LPlace.length := SizeOf(LPlace);
LIsRestore := GetWindowPlacement(LHwnd, @LPlace) and
(((LPlace.flags and WPF_RESTORETOMAXIMIZED) <> 0) or
(LPlace.showCmd = SW_SHOWMINIMIZED));
LogLine(Format('[winstate] resize zoomed=%s showCmd=%d flags=%d restore=%s want=%s',
GetWindowPlacement(LHwnd, @LPlace);
LogLine(Format('[winstate] resize zoomed=%s showCmd=%d flags=%d fg=%s want=%s',
[BoolToStr(IsZoomed(LHwnd), True), LPlace.showCmd, LPlace.flags,
BoolToStr(LIsRestore, True), BoolToStr(FWantMaximized, True)]));
BoolToStr(not LIsRestore, True), BoolToStr(FWantMaximized, True)]));
if LIsRestore then
begin
// Re-apply after FMX has finished sizing; doing it inline loses the race.
// Not a user gesture — never record it as intent. If it dropped a window
// the user had left maximised, put it back (queued: inline loses the race
// against FMX's own sizing).
if FWantMaximized and (not IsZoomed(LHwnd)) and (not FRestoring) then
begin
FRestoring := True;
@@ -1552,7 +1555,7 @@ begin
ShowWindow(LH, SW_SHOWMAXIMIZED);
end);
end;
Exit; // never record a restore as intent
Exit;
end;
if FRestoring then Exit; // our own ShowWindow's resize
FWantMaximized := IsZoomed(LHwnd); // genuine user gesture