fix(hotkeys): attach to the foreground thread to focus the buried dialog
The autofill helper attaches the caller to the TARGET thread, but the dialog runs on our own thread, so the attach was a no-op and Windows kept refusing the foreground change - the dialog rose without focus. Attaching to the current foreground thread instead is what grants the call.
This commit is contained in:
+2
-1
@@ -185,7 +185,8 @@ plus bas.
|
||||
- **Deuxième cause, côté Delphi** : `Ctrl+Shift+Q` et `Ctrl+Shift+A` appellent `RestoreWindowFromTray` **avant** que JS ne puisse refuser — un garde JS arrive trop tard par construction. Restaurer la fenêtre propriétaire d'une modale native laisse le dialogue en rade derrière tout le reste : l'app semble gelée alors qu'elle est simplement *désactivée* par une boîte devenue introuvable (au point qu'il fallait un utilitaire de capture d'écran pour la faire réapparaître).
|
||||
- **Garde** : `NativeModalActive` teste `IsWindowEnabled` sur notre fenêtre — Windows la désactive tant qu'une modale native lui appartient. Posé en tête des **quatre** points d'entrée hotkey (quick search hotkey + menu tray, new entry, autofill). Il ne se contente pas de refuser : il **ramène le dialogue au premier plan** via `GetLastActivePopup`, ce qui est précisément ce dont l'utilisateur a besoin.
|
||||
- **ATTENDU après correctif** : pendant l'« Enregistrer sous », n'importe quel hotkey **fait réapparaître le dialogue au premier plan, avec le focus**, et une **notification tray** explique que les raccourcis restent inactifs tant qu'il n'a pas répondu.
|
||||
- **Deux pièges du message et du focus** : `SetForegroundWindow` seul est **refusé** — le hotkey vient d'une autre application, donc nous ne sommes pas le processus de premier plan ; il faut `ForceForegroundWindow` (`AttachThreadInput`), désormais exposé par `PM.Bridge`. Et le message passe par une **balloon tray**, pas par un toast : notre fenêtre est désactivée et derrière le dialogue, rien de rendu dans la page ne serait visible.
|
||||
- **Piège du focus** : `SetForegroundWindow` est **refusé** à un processus qui n'est pas déjà devant, et le hotkey vient d'une autre application. Il faut attacher notre file d'entrées au thread de la **fenêtre de premier plan** (VS Code). Réutiliser le helper d'autofill ne marche **pas** : il attache au thread de la *cible*, or le dialogue appartient à notre propre thread — l'attache est un no-op et le dialogue remontait sans jamais recevoir le focus.
|
||||
- **Piège du message** : une **balloon tray**, pas un toast — notre fenêtre est désactivée et derrière le dialogue, rien de rendu dans la page ne serait visible. Elle suit le réglage « alertes tray » : désactivé, tu auras le focus sans le message.
|
||||
|
||||
### B14 · dialogues natifs ouverts par un clic programmatique
|
||||
- **ACTION** : `A` ouverte + **dirty** → (a) Settings → *Import vault* ; (b) télécharger une pièce jointe (fallback web) ; (c) *Upload icon* ; (d) joindre un fichier.
|
||||
|
||||
@@ -272,12 +272,6 @@ type
|
||||
read FOnQuickSearchHotkey write FOnQuickSearchHotkey;
|
||||
end;
|
||||
|
||||
// Bring a window to the foreground despite the Win10/11 anti-focus-stealing
|
||||
// rules (AttachThreadInput + poll). Exposed because the host needs it to raise
|
||||
// a native dialog of ours that got buried — plain SetForegroundWindow is
|
||||
// refused when we are not the foreground process.
|
||||
function ForceForegroundWindow(ATargetHwnd: HWND): Boolean;
|
||||
|
||||
implementation
|
||||
|
||||
uses
|
||||
@@ -313,6 +307,7 @@ function UnhookWinEvent(hWinEventHook: THandle): BOOL;
|
||||
// Defined further down but needed by the constructor / MinimizeToTray.
|
||||
function IsOwnProcessWindow(AHwnd: HWND): Boolean; forward;
|
||||
function IsShellWindow(AHwnd: HWND): Boolean; forward;
|
||||
function ForceForegroundWindow(ATargetHwnd: HWND): Boolean; forward;
|
||||
procedure ForegroundEventProc(hWinEventHook: THandle; AEvent: DWORD; AWnd: HWND;
|
||||
idObject, idChild: LongInt; idEventThread, dwmsEventTime: DWORD); stdcall; forward;
|
||||
|
||||
|
||||
@@ -514,10 +514,22 @@ begin
|
||||
if (LPopup <> 0) and (LPopup <> LHwnd) and IsWindow(LPopup) then
|
||||
begin
|
||||
ShowWindow(LPopup, SW_SHOW);
|
||||
// Not plain SetForegroundWindow: the hotkey is pressed from ANOTHER
|
||||
// application, so we are not the foreground process and Windows refuses
|
||||
// the call — the dialog was raised but never focused.
|
||||
ForceForegroundWindow(LPopup);
|
||||
// Windows refuses SetForegroundWindow to a process that isn't already in
|
||||
// front, and the hotkey comes from ANOTHER application. The way through is
|
||||
// to attach our input queue to the CURRENT FOREGROUND thread — not to the
|
||||
// dialog's, which is our own thread and would make the attach a no-op
|
||||
// (that is why the autofill helper, written for cross-process targets,
|
||||
// left the dialog raised but unfocused).
|
||||
var LFgThread := GetWindowThreadProcessId(GetForegroundWindow, nil);
|
||||
var LOurThread := GetCurrentThreadId;
|
||||
var LAttached := (LFgThread <> 0) and (LFgThread <> LOurThread) and
|
||||
AttachThreadInput(LOurThread, LFgThread, True);
|
||||
try
|
||||
BringWindowToTop(LPopup);
|
||||
SetForegroundWindow(LPopup);
|
||||
finally
|
||||
if LAttached then AttachThreadInput(LOurThread, LFgThread, False);
|
||||
end;
|
||||
// A tray balloon, not a toast: our own window is disabled and sits behind
|
||||
// the dialog, so nothing rendered in the page would be seen.
|
||||
FBridge.ShowBalloon('Finish the open dialog',
|
||||
|
||||
Reference in New Issue
Block a user