feat(hotkeys): chaque raccourci se desactive (Suppr pendant la capture)

Off = vk 0, non enregistre : la combinaison reste libre pour les autres
applis. Combinaisons retenues en pref DPAPI et rejouees au demarrage, pour
qu'un raccourci coupe le reste coffre ferme. Esc annule enfin la capture au
lieu de fermer les reglages.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
r-zakarya
2026-10-05 18:15:29 +01:00
parent 27afc4e8ca
commit 28f8a42846
5 changed files with 54 additions and 7 deletions
+13
View File
@@ -198,6 +198,19 @@ Retour Delphi → JS via `WebBrowser.ExecuteJavaScript('Bridge.onX(...)')`.
Combos autofill configurables depuis Settings (synced en DB Combos autofill configurables depuis Settings (synced en DB
via `settings_json`). via `settings_json`).
**Desactiver un raccourci (2026-10-05)** : clic sur le bouton, puis **Suppr**
(ou Retour arriere) → `key: ''` → vk 0, que Delphi n'enregistre pas ; libelle
*Off*. Les combinaisons sont aussi **retenues en pref DPAPI `hotkeys`** (la
requete `autofill/hotkeys` telle quelle, hors suspension de capture) et
**rejouees au demarrage** a la place des valeurs par defaut : `settings_json`
n'arrive qu'apres la connexion, et un `Ctrl+Shift+A` desactive restait pris
tant que le coffre etait ferme. Piege corrige au passage : l'Esc des reglages
(ecouteur `document` en capture, enregistre avant) fermait Settings au lieu
d'annuler la capture — il ignore Esc tant qu'un `[data-capturing="1"]` existe.
**La barrette remplit meme raccourcis coupes** : `onAutofillRequest` recoit
`fromBar`, l'option « Autofill with global hotkey » ne bloque que les
raccourcis.
## Fill bar (remplir à la souris) ## Fill bar (remplir à la souris)
Menu du tray → **Fill bar** : une barrette de trois Menu du tray → **Fill bar** : une barrette de trois
+18 -4
View File
@@ -364,7 +364,13 @@ begin
FBridge.OnNewEntryHotkey := BridgeNewEntryHotkey; FBridge.OnNewEntryHotkey := BridgeNewEntryHotkey;
FBridge.OnQuickSearchRequest := BridgeQuickSearchRequest; FBridge.OnQuickSearchRequest := BridgeQuickSearchRequest;
FBridge.OnQuickSearchHotkey := BridgeQuickSearchHotkey; FBridge.OnQuickSearchHotkey := BridgeQuickSearchHotkey;
FBridge.RegisterAutofillHotkey; // Ctrl+Shift+L active from startup // Les combinaisons de la derniere session (dont les raccourcis desactives),
// sinon les valeurs par defaut.
var LSavedHotkeys := PM.UserPrefs.GetPref('hotkeys');
if LSavedHotkeys <> '' then
HandleBridgeCommand('autofill/hotkeys', LSavedHotkeys)
else
FBridge.RegisterAutofillHotkey; // Ctrl+Shift+L active from startup
FBridge.ApplyTitleBarTheme(True); // dark by default, JS may toggle later FBridge.ApplyTitleBarTheme(True); // dark by default, JS may toggle later
// Apres le theme et apres OnTrayRestore : la barrette se peint elle-meme, et // Apres le theme et apres OnTrayRestore : la barrette se peint elle-meme, et
// son bouton Open passe par cet evenement. // son bouton Open passe par cet evenement.
@@ -1146,6 +1152,13 @@ begin
// Ids, not labels: JS owns the wording AND uses them to flag the exact // Ids, not labels: JS owns the wording AND uses them to flag the exact
// row in Settings. A combo the user cleared (vk=0) is never listed. // row in Settings. A combo the user cleared (vk=0) is never listed.
var LFailed := ''; var LFailed := '';
// RETENU EN PREF DPAPI pour etre rejoue au demarrage : les combinaisons
// vivent dans settings_json, lu seulement apres la connexion - un raccourci
// desactive restait donc pris par les valeurs par defaut tant que le coffre
// etait ferme. Pas la suspension de la capture (sans full_vk). Rien de
// secret dedans.
if GetParam('full_vk') <> '' then
PM.UserPrefs.SetPref('hotkeys', AParams);
// Quick-search combo is set unconditionally (independent of the // Quick-search combo is set unconditionally (independent of the
// autofill enabled flag) so the picker stays armed even when the // autofill enabled flag) so the picker stays armed even when the
// autofill hotkeys are turned off. // autofill hotkeys are turned off.
@@ -1198,9 +1211,10 @@ begin
// One notification for the whole batch, naming the combos that failed — // One notification for the whole batch, naming the combos that failed —
// empty string = everything registered. // empty string = everything registered.
LFailed := LFailed.TrimRight([',', ' ']); LFailed := LFailed.TrimRight([',', ' ']);
WebBrowser.ExecuteJavaScript( if FBrowserInitialized then
'if(window.Bridge&&Bridge.onAutofillHotkeysResult)' + WebBrowser.ExecuteJavaScript(
'Bridge.onAutofillHotkeysResult("' + LFailed.Replace('"', '') + '")'); 'if(window.Bridge&&Bridge.onAutofillHotkeysResult)' +
'Bridge.onAutofillHotkeysResult("' + LFailed.Replace('"', '') + '")');
end end
// execute: JS has matched an entry, decrypted the password, and is // execute: JS has matched an entry, decrypted the password, and is
+1 -1
View File
@@ -734,7 +734,7 @@
<button class="btn btn-ghost btn-sm hotkey-capture-btn" id="settingNewEntryCombo">Ctrl+Shift+A</button> <button class="btn btn-ghost btn-sm hotkey-capture-btn" id="settingNewEntryCombo">Ctrl+Shift+A</button>
</div> </div>
<p style="font-size:11px;color:var(--text-faint);margin:6px 0 0;line-height:1.4"> <p style="font-size:11px;color:var(--text-faint);margin:6px 0 0;line-height:1.4">
Click a button, then press your new combo. Needs Ctrl, Alt or Win + a letter / digit / F-key. Click a button, then press your new combo. Needs Ctrl, Alt or Win + a letter / digit / F-key. Press Delete instead to turn that hotkey off.
</p> </p>
<button class="btn btn-ghost btn-sm" id="settingAutofillResetHotkeys" style="margin-top:6px"> <button class="btn btn-ghost btn-sm" id="settingAutofillResetHotkeys" style="margin-top:6px">
Reset to defaults Reset to defaults
+4 -1
View File
@@ -51,7 +51,7 @@ function renderHotkeyConflicts() {
// Format a combo for human display: "Ctrl+Shift+L". // Format a combo for human display: "Ctrl+Shift+L".
function autofillComboLabel(c) { function autofillComboLabel(c) {
if (!c || !c.key) return '— not set —'; if (!c || !c.key) return 'Off';
const parts = []; const parts = [];
if (c.ctrl) parts.push('Ctrl'); if (c.ctrl) parts.push('Ctrl');
if (c.alt) parts.push('Alt'); if (c.alt) parts.push('Alt');
@@ -92,6 +92,9 @@ function autofillComboValid(c) {
function autofillCaptureFromEvent(e) { function autofillCaptureFromEvent(e) {
const k = e.key; const k = e.key;
if (k === 'Escape') return 'cancel'; if (k === 'Escape') return 'cancel';
// Suppr / Retour arriere pendant la capture = raccourci DESACTIVE : il
// n'est plus enregistre, la combinaison reste libre pour une autre appli.
if (k === 'Delete' || k === 'Backspace') return 'off';
// Ignore pure-modifier keydowns (user is still building the combo). // Ignore pure-modifier keydowns (user is still building the combo).
if (k === 'Control' || k === 'Shift' || k === 'Alt' || if (k === 'Control' || k === 'Shift' || k === 'Alt' ||
k === 'Meta' || k === 'OS') return null; k === 'Meta' || k === 'OS') return null;
+18 -1
View File
@@ -8043,6 +8043,9 @@ async function init() {
if (e.key !== 'Escape') return; if (e.key !== 'Escape') return;
if (!$('#settingsPanel').classList.contains('is-open')) return; if (!$('#settingsPanel').classList.contains('is-open')) return;
if (document.querySelector('.modal:not(.is-hidden)')) return; if (document.querySelector('.modal:not(.is-hidden)')) return;
// Capture de raccourci armee : Esc l'annule (son propre ecouteur,
// enregistre APRES celui-ci), il ne ferme pas les reglages.
if (document.querySelector('[data-capturing="1"]')) return;
// Busy overlay up (sync/backup running) — don't close under it. // Busy overlay up (sync/backup running) — don't close under it.
if ($('#busyOverlay') && !$('#busyOverlay').classList.contains('is-hidden')) return; if ($('#busyOverlay') && !$('#busyOverlay').classList.contains('is-hidden')) return;
// When the search box has focus AND a query, the input's own // When the search box has focus AND a query, the input's own
@@ -8390,7 +8393,7 @@ async function init() {
btn.dataset.capturing = '1'; btn.dataset.capturing = '1';
btn.classList.add('is-capturing'); btn.classList.add('is-capturing');
const original = btn.textContent; const original = btn.textContent;
btn.textContent = 'Press combo… (Esc to cancel)'; btn.textContent = 'Press combo… (Esc cancel · Del off)';
// Free our own combos so they reach this listener instead of // Free our own combos so they reach this listener instead of
// firing their hotkey. Restored by finish() / the success path. // firing their hotkey. Restored by finish() / the success path.
hotkeyPushSilent = true; hotkeyPushSilent = true;
@@ -8445,6 +8448,20 @@ async function init() {
return; return;
} }
if (captured === 'cancel') { finish(true); return; } if (captured === 'cancel') { finish(true); return; }
// Cle vide = vk 0 : Delphi n'enregistre pas ce raccourci.
if (captured === 'off') {
const off = { ctrl: false, shift: false, alt: false, win: false, key: '' };
if (kind === 'full') state.autofillHotkeyFull = off;
else if (kind === 'password') state.autofillHotkeyPwd = off;
else if (kind === 'totp') state.autofillHotkeyTotp = off;
else if (kind === 'quickSearch') state.quickSearchHotkey = off;
else /* newEntry */ state.newEntryHotkey = off;
btn.textContent = autofillComboLabel(off);
finish(false);
autofillPushHotkeys();
saveServerSettings();
return;
}
if (captured === 'invalid'){ if (captured === 'invalid'){
toast('Unsupported key — use a letter, digit or F-key', 'warning'); toast('Unsupported key — use a letter, digit or F-key', 'warning');
finish(true); finish(true);