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 <noreply@anthropic.com>
This commit is contained in:
@@ -165,8 +165,12 @@ type
|
|||||||
// without a stored username.
|
// without a stored username.
|
||||||
// AUsernameOnly = True → type ONLY the username (no Tab, no password);
|
// AUsernameOnly = True → type ONLY the username (no Tab, no password);
|
||||||
// used by the quick-search "autofill username" action.
|
// 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;
|
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 SecureClipboard: TSecureClipboard read FSecureClipboard;
|
||||||
property TrayAdded: Boolean read FTrayAdded;
|
property TrayAdded: Boolean read FTrayAdded;
|
||||||
property AutofillRegistered: Boolean read FAutofillRegistered;
|
property AutofillRegistered: Boolean read FAutofillRegistered;
|
||||||
@@ -638,6 +642,16 @@ begin
|
|||||||
LFormHwnd := MainFormHWND(FMainForm);
|
LFormHwnd := MainFormHWND(FMainForm);
|
||||||
LAppHwnd := FindFMXAppWindow;
|
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,
|
// Reverse order: show the app proxy first so the taskbar entry comes back,
|
||||||
// then show and foreground the form.
|
// then show and foreground the form.
|
||||||
if LAppHwnd <> 0 then
|
if LAppHwnd <> 0 then
|
||||||
@@ -1124,53 +1138,66 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TPMBridge.ExecuteAutofill(ATargetHWND: HWND;
|
procedure TPMBridge.ExecuteAutofill(ATargetHWND: HWND;
|
||||||
const AUsername, APassword: string; AUsernameOnly: Boolean = False);
|
const AUsername, APassword: string; AUsernameOnly: Boolean = False;
|
||||||
|
ARestoreAfter: Boolean = False);
|
||||||
const
|
const
|
||||||
MinimizeSettleMs = 80;
|
MinimizeSettleMs = 80;
|
||||||
FocusSettleDelayMs = 120;
|
FocusSettleDelayMs = 120;
|
||||||
var
|
var
|
||||||
OwnFormHwnd: HWND;
|
OwnFormHwnd: HWND;
|
||||||
|
LMinimizedSelf: Boolean;
|
||||||
begin
|
begin
|
||||||
OwnFormHwnd := MainFormHWND(FMainForm);
|
OwnFormHwnd := MainFormHWND(FMainForm);
|
||||||
|
LMinimizedSelf := False;
|
||||||
if GetForegroundWindow = OwnFormHwnd then
|
if GetForegroundWindow = OwnFormHwnd then
|
||||||
begin
|
begin
|
||||||
ShowWindow(OwnFormHwnd, SW_MINIMIZE);
|
ShowWindow(OwnFormHwnd, SW_MINIMIZE);
|
||||||
|
LMinimizedSelf := True;
|
||||||
Sleep(MinimizeSettleMs);
|
Sleep(MinimizeSettleMs);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
if ATargetHWND <> 0 then
|
try
|
||||||
ForceForegroundWindow(ATargetHWND);
|
if ATargetHWND <> 0 then
|
||||||
|
ForceForegroundWindow(ATargetHWND);
|
||||||
|
|
||||||
WaitForModifierRelease(1000);
|
WaitForModifierRelease(1000);
|
||||||
Sleep(FocusSettleDelayMs);
|
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;
|
SendSelectAllAndDelete;
|
||||||
Sleep(60);
|
Sleep(60);
|
||||||
SendUnicodeString(AUsername);
|
SendUnicodeString(AUsername);
|
||||||
Exit;
|
Sleep(200);
|
||||||
end;
|
SendVKey(VK_TAB);
|
||||||
|
Sleep(200);
|
||||||
if AUsername = '' then
|
|
||||||
begin
|
|
||||||
SendSelectAllAndDelete;
|
SendSelectAllAndDelete;
|
||||||
Sleep(60);
|
Sleep(60);
|
||||||
SendUnicodeString(APassword);
|
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;
|
end;
|
||||||
|
|
||||||
SendSelectAllAndDelete;
|
|
||||||
Sleep(60);
|
|
||||||
SendUnicodeString(AUsername);
|
|
||||||
Sleep(200);
|
|
||||||
SendVKey(VK_TAB);
|
|
||||||
Sleep(200);
|
|
||||||
SendSelectAllAndDelete;
|
|
||||||
Sleep(60);
|
|
||||||
SendUnicodeString(APassword);
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
end.
|
end.
|
||||||
|
|||||||
@@ -1466,7 +1466,11 @@ begin
|
|||||||
TTimer(Sender).Enabled := False;
|
TTimer(Sender).Enabled := False;
|
||||||
TTimer(Sender).Free;
|
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;
|
ForegroundAfter := GetForegroundWindow;
|
||||||
LogLine(Format('Autofill executed — target=%s, foreground_after=%s, match=%s',
|
LogLine(Format('Autofill executed — target=%s, foreground_after=%s, match=%s',
|
||||||
[IntToHex(TargetHwnd, 8), IntToHex(ForegroundAfter, 8),
|
[IntToHex(TargetHwnd, 8), IntToHex(ForegroundAfter, 8),
|
||||||
|
|||||||
Reference in New Issue
Block a user