fix(autofill): vraies touches vers un visualiseur VNC

VK_PACKET (texte Unicode) n'est pas retransmis par TigerVNC : le Mac
distant recevait un V par caractere. Cible VNC -> touches du clavier via
VkKeyScanEx dans la disposition de la cible, modificateurs compris.
Les autres fenetres gardent l'envoi Unicode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
r-zakarya
2026-10-14 13:34:47 +01:00
parent 6397ed008a
commit a3e63ee8f1
2 changed files with 101 additions and 4 deletions
+9
View File
@@ -1443,6 +1443,15 @@ d'accord.
### Contraintes connues autofill
- **VNC (2026-10-14)** : les caracteres partent en `KEYEVENTF_UNICODE`
(`VK_PACKET`), que TigerVNC & co ne savent pas retransmettre - un Mac
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.
- **UIPI** : SendInput vers process élevé bloqué. Notepad++ admin,
regedit → autofill silencieusement no-op. Tester avec apps non-élevées.
- **Chrome password manager** : si Chrome a des credentials sauvegardés
+92 -4
View File
@@ -2076,6 +2076,91 @@ begin
SendInput(LCount, @LInputs[0], SizeOf(TInput));
end;
function QueryFullProcessImageNameW(hProcess: THandle; dwFlags: DWORD;
lpExeName: PWideChar; var lpdwSize: DWORD): BOOL; stdcall;
external kernel32 name 'QueryFullProcessImageNameW';
/// UN VISUALISEUR VNC NE SAIT PAS TAPER DE L'UNICODE. SendUnicodeString envoie
/// chaque caractere en VK_PACKET ; une fenetre Windows le comprend, mais VNC
/// (TigerVNC, RealVNC, TightVNC, UltraVNC) traduit les VRAIES touches vers la
/// machine distante et ne sait que faire de VK_PACKET : un Mac recevait
/// "VVVVVV", un V par caractere. Pour lui on tape les touches du clavier.
/// ponytail: reconnu au nom de l'exe ("vnc") ; un autre client a distance qui
/// aurait le meme defaut s'ajouterait ici, ou deviendrait un reglage.
function TargetTypesByKeyCodes(AHwnd: HWND): Boolean;
const
PROCESS_QUERY_LIMITED_INFORMATION = $1000;
var
LPid, LLen: DWORD;
LProc: THandle;
LName: array[0..MAX_PATH] of WideChar;
begin
Result := False;
LPid := 0;
GetWindowThreadProcessId(AHwnd, LPid);
if LPid = 0 then Exit;
LProc := OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, False, LPid);
if LProc = 0 then Exit;
try
LLen := Length(LName);
if QueryFullProcessImageNameW(LProc, 0, @LName[0], LLen) then
Result := ExtractFileName(string(LName)).ToLower.Contains('vnc');
finally
CloseHandle(LProc);
end;
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);
var
LInputs: TArray<TInput>;
LCount: Integer;
procedure Key(AVk: Word; AUp: Boolean);
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;
if AUp then LInputs[LCount].ki.dwFlags := KEYEVENTF_KEYUP;
Inc(LCount);
end;
var
I: Integer;
LScan: SHORT;
LVk, LMods: Byte;
begin
for I := 1 to Length(AText) do
begin
LScan := VkKeyScanEx(AText[I], ALayout);
if LScan = -1 then
begin
SendUnicodeString(AText[I]);
Continue;
end;
LVk := Lo(Word(LScan));
LMods := Hi(Word(LScan));
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);
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);
SendInput(LCount, @LInputs[0], SizeOf(TInput));
end;
end;
procedure SendTextTo(const AText: string; AByKeys: Boolean; ALayout: HKL);
begin
if AByKeys then SendKeyCodeString(AText, ALayout)
else SendUnicodeString(AText);
end;
// Send one virtual-key press (down+up).
procedure SendVKey(AVk: Word);
var
@@ -2290,8 +2375,11 @@ begin
LSw := TStopwatch.StartNew;
WaitForModifierRelease(1000);
ForceReleaseModifiers; // timeout hit with keys still down → clean state
var LByKeys := (ATargetHWND <> 0) and TargetTypesByKeyCodes(ATargetHWND);
var LLayout := GetKeyboardLayout(GetWindowThreadProcessId(ATargetHWND, nil));
FLastFillWaits := Format('foreground=%d ms, modifiers=%d ms',
[LFgMs, LSw.ElapsedMilliseconds]);
if LByKeys then FLastFillWaits := FLastFillWaits + ', typed as key codes (VNC)';
Sleep(FocusSettleDelayMs);
// Never type into our own window: if the target refused the foreground,
@@ -2315,19 +2403,19 @@ begin
if AUsernameOnly then
begin
ClearFieldIfWanted;
SendUnicodeString(AUsername);
SendTextTo(AUsername, LByKeys, LLayout);
Exit;
end;
if AUsername = '' then
begin
ClearFieldIfWanted;
SendUnicodeString(APassword);
SendTextTo(APassword, LByKeys, LLayout);
Exit;
end;
ClearFieldIfWanted;
SendUnicodeString(AUsername);
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.
@@ -2339,7 +2427,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.
SendUnicodeString(APassword);
SendTextTo(APassword, LByKeys, LLayout);
finally
// 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.