fix(hotkeys): find the native dialog by enumerating the thread windows

GetLastActivePopup returns the form itself - the common Save dialog is not an owned popup - so the code that raises it never ran. It does live on our thread with the standard #32770 class, which EnumThreadWindows finds regardless of its owner.

Logging confirmed two things worth keeping: WM_HOTKEY is dispatched during the dialog modal loop, and IsWindowEnabled detects the state correctly.
This commit is contained in:
r-zakarya
2026-08-01 19:03:03 +01:00
parent d6701a8994
commit 4a78dc2e90
2 changed files with 30 additions and 2 deletions
+27 -1
View File
@@ -201,6 +201,11 @@ implementation
{$R *.fmx}
// Result slot for EnumOwnDialogProc — the enumeration is main-thread only and
// consumed immediately, so a unit variable is enough.
var
GFoundDialogHwnd: HWND = 0;
// Forward — used by WebBrowserInitialized (which sits above the actual
// definition lower in the unit).
function MaskAccessToken(const AUrl: string): string; forward;
@@ -503,6 +508,24 @@ end;
// the user can no longer find. Every hotkey entry point checks this first — and
// rather than just refusing, it surfaces the dialog, which is what the user
// actually needs.
// The common Save/Open dialog is NOT an owned popup of the form —
// GetLastActivePopup returns the form itself — but it does live on our thread
// and carries the standard dialog class. Enumerating the thread's windows finds
// it whoever its owner turns out to be.
function EnumOwnDialogProc(AWnd: HWND; AParam: LPARAM): BOOL; stdcall;
var
LCls: array[0..63] of Char;
begin
Result := True; // keep enumerating
if (AWnd = HWND(AParam)) or (not IsWindowVisible(AWnd)) then Exit;
if GetClassName(AWnd, LCls, Length(LCls)) = 0 then Exit;
if SameText(LCls, '#32770') then
begin
GFoundDialogHwnd := AWnd;
Result := False; // stop
end;
end;
function TMainForm.NativeModalActive: Boolean;
var
LHwnd, LPopup: HWND;
@@ -510,7 +533,10 @@ begin
LHwnd := WindowHandleToPlatform(Self.Handle).Wnd;
Result := (LHwnd <> 0) and (not IsWindowEnabled(LHwnd));
if not Result then Exit;
LPopup := GetLastActivePopup(LHwnd);
GFoundDialogHwnd := 0;
EnumThreadWindows(GetCurrentThreadId, @EnumOwnDialogProc, LPARAM(LHwnd));
LPopup := GFoundDialogHwnd;
if LPopup = 0 then LPopup := GetLastActivePopup(LHwnd); // fallback
if (LPopup <> 0) and (LPopup <> LHwnd) and IsWindow(LPopup) then
begin
ShowWindow(LPopup, SW_SHOW);