From ca54c39c7524c3d52bbd2062c3e0c57eef23efcc Mon Sep 17 00:00:00 2001 From: r-zakarya <82443831+r-zakarya@users.noreply.github.com> Date: Sat, 25 Jul 2026 22:42:05 +0100 Subject: [PATCH] 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 --- delphi-backend/UMainForm.pas | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/delphi-backend/UMainForm.pas b/delphi-backend/UMainForm.pas index fc4d6a8..488d696 100644 --- a/delphi-backend/UMainForm.pas +++ b/delphi-backend/UMainForm.pas @@ -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