fix(autofill): AltGr et caracteres sans touche vers VNC

AltGr envoye en Ctrl gauche + Alt droit etendu, avec scancodes : "@" et
"#" en AZERTY arrivaient sans AltGr. Un caractere sans touche dans la
disposition locale est saute et compte dans le journal (copier a la place).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
r-zakarya
2026-10-14 13:45:33 +01:00
parent a3e63ee8f1
commit 9999dd113d
2 changed files with 50 additions and 21 deletions
+41 -18
View File
@@ -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<TInput>;
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