diff --git a/delphi-backend/Source/PM.Bridge.pas b/delphi-backend/Source/PM.Bridge.pas index c214a98..61d9029 100644 --- a/delphi-backend/Source/PM.Bridge.pas +++ b/delphi-backend/Source/PM.Bridge.pas @@ -171,9 +171,10 @@ type // the caller trays it after the fill. // Returns False when nothing was typed: elevated target (UIPI would // silently drop the keystrokes) or focus never left our own window. + // AClearFirst: send Ctrl+A + Del before each field (user setting). function ExecuteAutofill(ATargetHWND: HWND; const AUsername, APassword: string; AUsernameOnly: Boolean = False; - ARestoreAfter: Boolean = False): Boolean; + ARestoreAfter: Boolean = False; AClearFirst: Boolean = True): Boolean; property SecureClipboard: TSecureClipboard read FSecureClipboard; property TrayAdded: Boolean read FTrayAdded; property AutofillRegistered: Boolean read FAutofillRegistered; @@ -1187,10 +1188,21 @@ end; function TPMBridge.ExecuteAutofill(ATargetHWND: HWND; const AUsername, APassword: string; AUsernameOnly: Boolean = False; - ARestoreAfter: Boolean = False): Boolean; + ARestoreAfter: Boolean = False; AClearFirst: Boolean = True): Boolean; const MinimizeSettleMs = 80; FocusSettleDelayMs = 120; + + // Ctrl+A + Del to empty the target field — skipped when the user turned + // "Clear the field before typing" off (Ctrl+A isn't select-all everywhere: + // terminals, some remote desktops). + procedure ClearFieldIfWanted; + begin + if not AClearFirst then Exit; + SendSelectAllAndDelete; + Sleep(60); + end; + var OwnFormHwnd: HWND; begin @@ -1233,28 +1245,24 @@ begin // no password. Used by the quick-search right-click / Shift+Enter path. if AUsernameOnly then begin - SendSelectAllAndDelete; - Sleep(60); + ClearFieldIfWanted; SendUnicodeString(AUsername); Exit; end; if AUsername = '' then begin - SendSelectAllAndDelete; - Sleep(60); + ClearFieldIfWanted; SendUnicodeString(APassword); Exit; end; - SendSelectAllAndDelete; - Sleep(60); + ClearFieldIfWanted; SendUnicodeString(AUsername); Sleep(200); SendVKey(VK_TAB); Sleep(200); - SendSelectAllAndDelete; - Sleep(60); + ClearFieldIfWanted; SendUnicodeString(APassword); end; diff --git a/delphi-backend/UMainForm.pas b/delphi-backend/UMainForm.pas index d50f548..d75a1ae 100644 --- a/delphi-backend/UMainForm.pas +++ b/delphi-backend/UMainForm.pas @@ -105,6 +105,9 @@ type // True when the pending autofill should type ONLY the username (quick // search "autofill username" — right-click / Shift+Enter in fill mode). FAutofillPendingUserOnly: Boolean; + // Send Ctrl+A + Del before typing each field (user setting, default ON; + // OFF for targets where Ctrl+A isn't select-all — terminals, RDP). + FAutofillPendingClear: Boolean; // Created dynamically in FormCreate so the directive can pick either // TTMSFNCWebBrowser or TTMSFNCEdgeWebBrowser at compile time without // needing two .fmx variants. Aligned to Client to fill the remaining @@ -897,6 +900,7 @@ begin FAutofillPendingHWND := FAutofillTargetHWND; FAutofillPendingHide := GetParam('hide_after') = '1'; FAutofillPendingUserOnly := GetParam('field') = 'user'; + FAutofillPendingClear := GetParam('clear') <> '0'; // absent = ON FAutofillTargetHWND := 0; // Small timer so SetForegroundWindow has time to take effect before @@ -1449,7 +1453,7 @@ procedure TMainForm.AutofillTimerTick(Sender: TObject); var TargetHwnd: HWND; PendingUser, PendingPass: string; - HideAfter, UserOnly: Boolean; + HideAfter, UserOnly, ClearFirst: Boolean; ForegroundAfter: HWND; begin TargetHwnd := FAutofillPendingHWND; @@ -1457,11 +1461,13 @@ begin PendingPass := FAutofillPendingPass; HideAfter := FAutofillPendingHide; UserOnly := FAutofillPendingUserOnly; + ClearFirst := FAutofillPendingClear; FAutofillPendingHWND := 0; FAutofillPendingUser := ''; FAutofillPendingPass := ''; FAutofillPendingHide := False; FAutofillPendingUserOnly := False; + FAutofillPendingClear := False; TTimer(Sender).Enabled := False; TTimer(Sender).Free; @@ -1470,7 +1476,7 @@ begin // (quick-search beside the target), it stays visible during the fill; if it // started hidden, the MinimizeToTray below re-hides it anyway. var LFillOk := FBridge.ExecuteAutofill(TargetHwnd, PendingUser, PendingPass, - UserOnly, not HideAfter); + UserOnly, not HideAfter, ClearFirst); ForegroundAfter := GetForegroundWindow; LogLine(Format('Autofill executed — target=%s, foreground_after=%s, ok=%s', [IntToHex(TargetHwnd, 8), IntToHex(ForegroundAfter, 8), diff --git a/index.html b/index.html index 56c32c1..4e3c214 100644 --- a/index.html +++ b/index.html @@ -628,6 +628,20 @@ +
+ + Clear the field before typing + + Sends Ctrl+A + Del to empty the target field first. + Disable for apps where Ctrl+A isn't "select all" + (terminals, some remote desktops). + + + +
diff --git a/js/app.js b/js/app.js index d3d4b4b..5414a7a 100644 --- a/js/app.js +++ b/js/app.js @@ -141,7 +141,8 @@ const Bridge = (() => { cmd('cmd://autofill/execute?username=' + encodeURIComponent(username) + '&password=' + encodeURIComponent(password) + (hideAfter ? '&hide_after=1' : '') + - (field === 'user' ? '&field=user' : '')); + (field === 'user' ? '&field=user' : '') + + (state.autofillClearField ? '' : '&clear=0')); }, // Ask Delphi to bring the main window to front (used when the @@ -643,6 +644,7 @@ const state = { quickUnlockEnabled: localStorage.getItem('quickUnlockEnabled') === '1', recoveryConfigured: false, // refreshed by refreshRecoveryStatus on Settings open autofillEnabled: localStorage.getItem('autofillEnabled') !== '0', // default ON + autofillClearField: localStorage.getItem('autofillClearField') !== '0', // default ON // Hotkey combos. Each combo = { ctrl, shift, alt, win, key }. // key is the uppercase character or VK label ('A'..'Z', '0'..'9', // 'F1'..'F12'). Default: Ctrl+Shift+L / Ctrl+Shift+P. Combos are @@ -6341,6 +6343,7 @@ function openSettings() { $('#settingFaviconsRow').style.display = Bridge.active ? '' : 'none'; $('#settingFaviconActionsRow').style.display = Bridge.active ? 'flex' : 'none'; $('#settingAutofill').checked = state.autofillEnabled; + $('#settingAutofillClear').checked = state.autofillClearField; $('#settingAutofillRow').style.display = Bridge.active ? '' : 'none'; // Hotkey capture buttons — labels reflect current combos. $('#settingAutofillFullCombo').textContent = autofillComboLabel(state.autofillHotkeyFull); @@ -6750,6 +6753,9 @@ const SYNCED_SETTING_KEYS = [ // Unlock method (pw / pin / both). The PIN blob itself is device-local // DPAPI so this synced setting only carries the user's preferred mode. 'unlockMode', + // Send Ctrl+A + Del before typing each autofill field (default ON). + // OFF for targets where Ctrl+A isn't select-all (terminals, RDP). + 'autofillClearField', ]; // Sets `data-editor-position` on so CSS can swap the slideover @@ -6830,6 +6836,9 @@ async function loadServerSettings() { case 'unlockMode': localStorage.setItem('unlockMode', String(v || 'pw')); break; + case 'autofillClearField': + localStorage.setItem('autofillClearField', v ? '1' : '0'); + break; } }); // Apply visual settings immediately. @@ -7660,6 +7669,11 @@ async function init() { const lbl = autofillComboLabel(state.autofillHotkeyFull); toast(state.autofillEnabled ? ('Autofill enabled (' + lbl + ')') : 'Autofill disabled'); }); + $('#settingAutofillClear').addEventListener('change', e => { + state.autofillClearField = e.target.checked; + localStorage.setItem('autofillClearField', e.target.checked ? '1' : '0'); + saveServerSettings(); + }); // ---- Hotkey capture buttons ---- // Click → button label becomes "Press combo…" → next keydown captures.