diff --git a/CLAUDE.md b/CLAUDE.md index 574aae9..ad0c776 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1448,9 +1448,15 @@ d'accord. recevait un `V` par caractere. Cible dont l'exe contient « vnc » (`TargetTypesByKeyCodes`) → **vraies touches** (`SendKeyCodeString` : `VkKeyScanEx` dans la disposition de la cible, + Maj/Ctrl/Alt, AltGr = - Ctrl+Alt) ; un caractere sans touche repart en Unicode. Journal : `typed as - key codes (VNC)` sur la ligne `Autofill waits`. Eprouve sur un mot de passe - simple ; caracteres speciaux dependants de la disposition du Mac distant. + 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. - **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 28e2d7a..09ddf29 100644 --- a/delphi-backend/Source/PM.Bridge.pas +++ b/delphi-backend/Source/PM.Bridge.pas @@ -2111,53 +2111,71 @@ begin end; /// Le texte en VRAIES touches, dans la disposition clavier de la cible : chaque -/// caractere devient sa touche plus Maj / Ctrl / Alt (AltGr = Ctrl+Alt) selon -/// VkKeyScanEx. Un caractere sans touche dans cette disposition part en -/// Unicode, faute de mieux. -procedure SendKeyCodeString(const AText: string; ALayout: HKL); +/// caractere devient sa touche plus Maj / Ctrl / Alt selon VkKeyScanEx, avec +/// son scancode (certains clients VNC lisent la position physique). +/// 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; var LInputs: TArray; LCount: Integer; - procedure Key(AVk: Word; AUp: Boolean); + procedure Key(AVk: Word; AUp: Boolean; AExtended: Boolean = False); begin if LCount >= Length(LInputs) then SetLength(LInputs, LCount + 16); FillChar(LInputs[LCount], SizeOf(TInput), 0); LInputs[LCount].Itype := INPUT_KEYBOARD; LInputs[LCount].ki.wVk := AVk; + LInputs[LCount].ki.wScan := MapVirtualKeyEx(AVk, 0 {MAPVK_VK_TO_VSC}, ALayout); if AUp then LInputs[LCount].ki.dwFlags := KEYEVENTF_KEYUP; + if AExtended then + LInputs[LCount].ki.dwFlags := LInputs[LCount].ki.dwFlags or KEYEVENTF_EXTENDEDKEY; Inc(LCount); end; var I: Integer; LScan: SHORT; LVk, LMods: Byte; + LShift, LCtrl, LAlt: Boolean; begin + Result := 0; for I := 1 to Length(AText) do begin LScan := VkKeyScanEx(AText[I], ALayout); if LScan = -1 then begin - SendUnicodeString(AText[I]); + Inc(Result); Continue; end; LVk := Lo(Word(LScan)); LMods := Hi(Word(LScan)); + LShift := LMods and 1 <> 0; + LCtrl := LMods and 2 <> 0; + LAlt := LMods and 4 <> 0; LCount := 0; - if LMods and 1 <> 0 then Key(VK_SHIFT, False); - if LMods and 2 <> 0 then Key(VK_CONTROL, False); - if LMods and 4 <> 0 then Key(VK_MENU, False); + if LShift then Key(VK_LSHIFT, False); + if LCtrl then Key(VK_LCONTROL, False); + // Alt seul = Alt gauche ; avec Ctrl, c'est AltGr = Alt droit, etendu. + if LAlt and LCtrl then Key(VK_RMENU, False, True) + else if LAlt then Key(VK_LMENU, False); Key(LVk, False); Key(LVk, True); - if LMods and 4 <> 0 then Key(VK_MENU, True); - if LMods and 2 <> 0 then Key(VK_CONTROL, True); - if LMods and 1 <> 0 then Key(VK_SHIFT, True); + if LAlt and LCtrl then Key(VK_RMENU, True, True) + else if LAlt then Key(VK_LMENU, True); + if LCtrl then Key(VK_LCONTROL, True); + if LShift then Key(VK_LSHIFT, True); SendInput(LCount, @LInputs[0], SizeOf(TInput)); end; end; -procedure SendTextTo(const AText: string; AByKeys: Boolean; ALayout: HKL); +/// Rend le nombre de caracteres qui n'ont pas pu etre tapes (VNC seulement). +function SendTextTo(const AText: string; AByKeys: Boolean; ALayout: HKL): Integer; begin - if AByKeys then SendKeyCodeString(AText, ALayout) + Result := 0; + if AByKeys then Result := SendKeyCodeString(AText, ALayout) else SendUnicodeString(AText); end; @@ -2380,6 +2398,7 @@ begin FLastFillWaits := Format('foreground=%d ms, modifiers=%d ms', [LFgMs, LSw.ElapsedMilliseconds]); if LByKeys then FLastFillWaits := FLastFillWaits + ', typed as key codes (VNC)'; + var LMissing := 0; Sleep(FocusSettleDelayMs); // Never type into our own window: if the target refused the foreground, @@ -2403,19 +2422,19 @@ begin if AUsernameOnly then begin ClearFieldIfWanted; - SendTextTo(AUsername, LByKeys, LLayout); + Inc(LMissing, SendTextTo(AUsername, LByKeys, LLayout)); Exit; end; if AUsername = '' then begin ClearFieldIfWanted; - SendTextTo(APassword, LByKeys, LLayout); + Inc(LMissing, SendTextTo(APassword, LByKeys, LLayout)); Exit; end; ClearFieldIfWanted; - SendTextTo(AUsername, LByKeys, LLayout); + Inc(LMissing, SendTextTo(AUsername, LByKeys, 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. @@ -2427,8 +2446,12 @@ 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. - SendTextTo(APassword, LByKeys, LLayout); + Inc(LMissing, SendTextTo(APassword, LByKeys, LLayout)); finally + if LMissing > 0 then + FLastFillWaits := FLastFillWaits + Format( + ', %d character(s) have no key on this keyboard and were NOT typed - copy instead', + [LMissing]); // Re-register with the LIVE combo (user may have rebound it), not the // hardcoded default — otherwise a fill would reset new-entry to Ctrl+Shift+A. if FNewEntryHotkeyRegistered then