diff --git a/CLAUDE.md b/CLAUDE.md index ad0c776..6fc5b52 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1451,12 +1451,16 @@ d'accord. Ctrl+Alt). **AltGr = `VK_LCONTROL` + `VK_RMENU` etendu**, comme le vrai clavier : un Ctrl+Alt generique etait lu par TigerVNC comme deux modificateurs, et `@` (AltGr+0 en AZERTY) arrivait en `a`. Chaque touche porte - aussi son scancode (`MapVirtualKeyEx`). **Un caractere sans touche dans la - disposition locale** (arabe sur clavier francais) ne peut pas passer : il - est **saute et compte** - journal `N character(s) have no key on this - keyboard and were NOT typed - copy instead` ; la copie, que TigerVNC - synchronise, reste le recours. Eprouve vers un Mac : lettres, espaces, `@`, - `#` corrects, 19 caracteres arabes sautes et comptes. + aussi son scancode (`MapVirtualKeyEx`). **Texte melange (arabe + latin)** : + un caractere sans touche dans la disposition courante est cherche dans les + autres dispositions installees (`GetKeyboardLayoutList`) ; la fenetre cible + y est basculee (`WM_INPUTLANGCHANGEREQUEST`, attente que son fil l'ait + prise - TigerVNC traduit chaque touche avec la disposition de SON fil), et + la disposition d'origine est rendue a la fin. Seul un caractere qu'aucune + disposition installee ne sait taper est **saute et compte** - journal + `N character(s) have no key on this keyboard and were NOT typed - copy + instead`. Eprouve vers un Mac : `تاسيب... jhbs... @ #` complet, clavier + francais comme arabe actif. - **UIPI** : SendInput vers process élevé bloqué. Notepad++ admin, regedit → autofill silencieusement no-op. Tester avec apps non-élevées. diff --git a/delphi-backend/Source/PM.Bridge.pas b/delphi-backend/Source/PM.Bridge.pas index 09ddf29..7ab1ad8 100644 --- a/delphi-backend/Source/PM.Bridge.pas +++ b/delphi-backend/Source/PM.Bridge.pas @@ -2116,10 +2116,42 @@ end; /// ALTGR = Ctrl GAUCHE + Alt DROIT (touche etendue), comme le clavier le /// produit : un Ctrl+Alt generique etait lu par TigerVNC comme deux /// modificateurs ordinaires, et "@" (AltGr+0 en AZERTY) arrivait en "a". -/// Un caractere sans touche dans cette disposition (de l'arabe sur un clavier -/// francais) ne peut pas passer par VNC : il est saute et compte - la copie, -/// que TigerVNC synchronise, reste le recours. Rend le nombre de sautes. -function SendKeyCodeString(const AText: string; ALayout: HKL): Integer; +/// TEXTE MELANGE (arabe + latin) : un caractere sans touche dans la +/// disposition courante est cherche dans les AUTRES dispositions installees +/// sur le poste ; on y bascule la fenetre cible (TigerVNC traduit chaque touche +/// avec la disposition de SON fil), on tape, et la disposition d'origine est +/// rendue a la fin. Seul un caractere qu'aucune disposition installee ne sait +/// taper est saute et compte - la copie, que TigerVNC synchronise, reste alors +/// le recours. Rend le nombre de sautes. +function SwitchTargetLayout(ATarget: HWND; ALayout: HKL): Boolean; +var + LThread: DWORD; +begin + LThread := GetWindowThreadProcessId(ATarget, nil); + PostMessage(ATarget, WM_INPUTLANGCHANGEREQUEST, 0, LPARAM(ALayout)); + // La bascule est asynchrone : on attend qu'elle soit faite avant de taper, + // sinon les premieres touches partiraient encore dans l'ancienne. + for var I := 1 to 30 do + begin + if GetKeyboardLayout(LThread) = ALayout then Exit(True); + Sleep(10); + end; + Result := GetKeyboardLayout(LThread) = ALayout; +end; + +function LayoutForChar(ACh: WideChar; ACurrent: HKL): HKL; +var + LList: array[0..31] of HKL; + N, I: Integer; +begin + Result := 0; + N := GetKeyboardLayoutList(Length(LList), LList); + for I := 0 to N - 1 do + if (LList[I] <> ACurrent) and (VkKeyScanEx(ACh, LList[I]) <> -1) then + Exit(LList[I]); +end; + +function SendKeyCodeString(const AText: string; ATarget: HWND; ALayout: HKL): Integer; var LInputs: TArray; LCount: Integer; @@ -2142,10 +2174,21 @@ var LShift, LCtrl, LAlt: Boolean; begin Result := 0; + var LOriginal := ALayout; + try for I := 1 to Length(AText) do begin LScan := VkKeyScanEx(AText[I], ALayout); if LScan = -1 then + begin + var LOther := LayoutForChar(AText[I], ALayout); + if (LOther <> 0) and SwitchTargetLayout(ATarget, LOther) then + begin + ALayout := LOther; + LScan := VkKeyScanEx(AText[I], ALayout); + end; + end; + if LScan = -1 then begin Inc(Result); Continue; @@ -2169,13 +2212,18 @@ begin if LShift then Key(VK_LSHIFT, True); SendInput(LCount, @LInputs[0], SizeOf(TInput)); end; + finally + // Rendre a l'utilisateur la disposition qu'il avait choisie. + if ALayout <> LOriginal then SwitchTargetLayout(ATarget, LOriginal); + end; end; /// Rend le nombre de caracteres qui n'ont pas pu etre tapes (VNC seulement). -function SendTextTo(const AText: string; AByKeys: Boolean; ALayout: HKL): Integer; +function SendTextTo(const AText: string; AByKeys: Boolean; ATarget: HWND; + ALayout: HKL): Integer; begin Result := 0; - if AByKeys then Result := SendKeyCodeString(AText, ALayout) + if AByKeys then Result := SendKeyCodeString(AText, ATarget, ALayout) else SendUnicodeString(AText); end; @@ -2422,19 +2470,19 @@ begin if AUsernameOnly then begin ClearFieldIfWanted; - Inc(LMissing, SendTextTo(AUsername, LByKeys, LLayout)); + Inc(LMissing, SendTextTo(AUsername, LByKeys, ATargetHWND, LLayout)); Exit; end; if AUsername = '' then begin ClearFieldIfWanted; - Inc(LMissing, SendTextTo(APassword, LByKeys, LLayout)); + Inc(LMissing, SendTextTo(APassword, LByKeys, ATargetHWND, LLayout)); Exit; end; ClearFieldIfWanted; - Inc(LMissing, SendTextTo(AUsername, LByKeys, LLayout)); + Inc(LMissing, SendTextTo(AUsername, LByKeys, ATargetHWND, LLayout)); Sleep(200); // Real-VK send: a physically held Shift here turns Tab into Shift+Tab — // focus moves BACKWARD and the password lands in the username field. @@ -2446,7 +2494,7 @@ begin // Tab lands somewhere that is not an editable field (a link, a reveal // button, the document itself, as on the Reddit login form) it selects the // ENTIRE PAGE, and the password is then typed into nothing. - Inc(LMissing, SendTextTo(APassword, LByKeys, LLayout)); + Inc(LMissing, SendTextTo(APassword, LByKeys, ATargetHWND, LLayout)); finally if LMissing > 0 then FLastFillWaits := FLastFillWaits + Format(