From 3b32a621e8c3b2f345bc5d630ad8f8f64a5f6c65 Mon Sep 17 00:00:00 2001 From: r-zakarya <82443831+r-zakarya@users.noreply.github.com> Date: Sat, 11 Jul 2026 19:52:26 +0100 Subject: [PATCH] fix(quick-search): stop teleporting + hiding an already-open window Two bugs when Ctrl+Shift+Q fires while the app window is open beside the target app: - RestoreFromTray re-applied FSavedPlacement (captured at the LAST MinimizeToTray) to an already-visible window -> it jumped to a stale position. Now: visible and not iconic -> just SetForegroundWindow. - ExecuteAutofill minimizes our window when it is foreground (the user just clicked the entry) and never brought it back when hide_after was false. New ARestoreAfter param (= not HideAfter): restore with SW_SHOWNOACTIVATE after the fill, so the window returns to its position without stealing focus from the freshly-filled target. Co-Authored-By: Claude Opus 4.8 --- delphi-backend/Source/PM.Bridge.pas | 79 +++++++++++++++++++---------- delphi-backend/UMainForm.pas | 6 ++- 2 files changed, 58 insertions(+), 27 deletions(-) diff --git a/delphi-backend/Source/PM.Bridge.pas b/delphi-backend/Source/PM.Bridge.pas index b3be6e7..31d1ee1 100644 --- a/delphi-backend/Source/PM.Bridge.pas +++ b/delphi-backend/Source/PM.Bridge.pas @@ -165,8 +165,12 @@ type // without a stored username. // AUsernameOnly = True → type ONLY the username (no Tab, no password); // used by the quick-search "autofill username" action. + // ARestoreAfter = True → if the fill had to minimise our own window (it + // was foreground, e.g. quick-search pick), show it again afterwards + // WITHOUT activating it, so an app the user had open doesn't vanish. procedure ExecuteAutofill(ATargetHWND: HWND; - const AUsername, APassword: string; AUsernameOnly: Boolean = False); + const AUsername, APassword: string; AUsernameOnly: Boolean = False; + ARestoreAfter: Boolean = False); property SecureClipboard: TSecureClipboard read FSecureClipboard; property TrayAdded: Boolean read FTrayAdded; property AutofillRegistered: Boolean read FAutofillRegistered; @@ -638,6 +642,16 @@ begin LFormHwnd := MainFormHWND(FMainForm); LAppHwnd := FindFMXAppWindow; + // Already visible and not minimised (quick-search / focus hotkey while the + // window is open): just bring it to front. Re-applying FSavedPlacement — + // captured at the LAST MinimizeToTray — would teleport the window to a + // stale position the user has since moved away from. + if FMainForm.Visible and (not IsIconic(LFormHwnd)) then + begin + SetForegroundWindow(LFormHwnd); + Exit; + end; + // Reverse order: show the app proxy first so the taskbar entry comes back, // then show and foreground the form. if LAppHwnd <> 0 then @@ -1124,53 +1138,66 @@ begin end; procedure TPMBridge.ExecuteAutofill(ATargetHWND: HWND; - const AUsername, APassword: string; AUsernameOnly: Boolean = False); + const AUsername, APassword: string; AUsernameOnly: Boolean = False; + ARestoreAfter: Boolean = False); const MinimizeSettleMs = 80; FocusSettleDelayMs = 120; var OwnFormHwnd: HWND; + LMinimizedSelf: Boolean; begin OwnFormHwnd := MainFormHWND(FMainForm); + LMinimizedSelf := False; if GetForegroundWindow = OwnFormHwnd then begin ShowWindow(OwnFormHwnd, SW_MINIMIZE); + LMinimizedSelf := True; Sleep(MinimizeSettleMs); end; - if ATargetHWND <> 0 then - ForceForegroundWindow(ATargetHWND); + try + if ATargetHWND <> 0 then + ForceForegroundWindow(ATargetHWND); - WaitForModifierRelease(1000); - Sleep(FocusSettleDelayMs); + WaitForModifierRelease(1000); + Sleep(FocusSettleDelayMs); + + // Username-only: type just the username into the focused field, no Tab, + // no password. Used by the quick-search right-click / Shift+Enter path. + if AUsernameOnly then + begin + SendSelectAllAndDelete; + Sleep(60); + SendUnicodeString(AUsername); + Exit; + end; + + if AUsername = '' then + begin + SendSelectAllAndDelete; + Sleep(60); + SendUnicodeString(APassword); + Exit; + end; - // Username-only: type just the username into the focused field, no Tab, - // no password. Used by the quick-search right-click / Shift+Enter path. - if AUsernameOnly then - begin SendSelectAllAndDelete; Sleep(60); SendUnicodeString(AUsername); - Exit; - end; - - if AUsername = '' then - begin + Sleep(200); + SendVKey(VK_TAB); + Sleep(200); SendSelectAllAndDelete; Sleep(60); SendUnicodeString(APassword); - Exit; + finally + // We shoved our own window out of the way to give the target the + // foreground. If it was open before the fill (quick-search pick while + // the app sits next to the target), bring it back at its old position + // — SW_SHOWNOACTIVATE so the freshly-filled app keeps the focus. + if LMinimizedSelf and ARestoreAfter then + ShowWindow(OwnFormHwnd, SW_SHOWNOACTIVATE); end; - - SendSelectAllAndDelete; - Sleep(60); - SendUnicodeString(AUsername); - Sleep(200); - SendVKey(VK_TAB); - Sleep(200); - SendSelectAllAndDelete; - Sleep(60); - SendUnicodeString(APassword); end; end. diff --git a/delphi-backend/UMainForm.pas b/delphi-backend/UMainForm.pas index 51651c5..a9cee99 100644 --- a/delphi-backend/UMainForm.pas +++ b/delphi-backend/UMainForm.pas @@ -1466,7 +1466,11 @@ begin TTimer(Sender).Enabled := False; TTimer(Sender).Free; - FBridge.ExecuteAutofill(TargetHwnd, PendingUser, PendingPass, UserOnly); + // ARestoreAfter = not HideAfter: if the app was open before the hotkey + // (quick-search beside the target), it comes back after the fill; if it + // started hidden, the MinimizeToTray below re-hides it anyway. + FBridge.ExecuteAutofill(TargetHwnd, PendingUser, PendingPass, UserOnly, + not HideAfter); ForegroundAfter := GetForegroundWindow; LogLine(Format('Autofill executed — target=%s, foreground_after=%s, match=%s', [IntToHex(TargetHwnd, 8), IntToHex(ForegroundAfter, 8),