feat(hotkey): make the new-entry hotkey configurable + fix tray title capture

Part A — Ctrl+Shift+A (new entry from window title) is now editable like the
other 3 global hotkeys, following the same pattern end to end: state.newEntryHotkey
(synced), a capture button in Settings > Autofill, dup cross-check, reset-to-
defaults, autofillPushHotkeys, and Bridge.setAutofillHotkeys carrying ne_mods/
ne_vk. Delphi gets SetNewEntryHotkey + stores the live combo (FNewEntryHotkeyMods/
Vk) so ExecuteAutofill's post-fill re-register uses the user's combo instead of
resetting to Ctrl+Shift+A. UMainForm parses ne_* unconditionally (works with
autofill off).

Part B — new-entry from the tray pre-filled the title with "Password Manager":
GetForegroundWindow resolved to our own hidden window. Only capture the title
when the foreground window belongs to another process; otherwise pass '' (blank).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
r-zakarya
2026-07-21 19:49:48 +01:00
parent a8e0a3da00
commit 0d40328a24
5 changed files with 79 additions and 9 deletions
+2
View File
@@ -82,11 +82,13 @@ function autofillPushHotkeys() {
localStorage.setItem('autofillHotkeyFull', JSON.stringify(state.autofillHotkeyFull));
localStorage.setItem('autofillHotkeyPwd', JSON.stringify(state.autofillHotkeyPwd));
localStorage.setItem('quickSearchHotkey', JSON.stringify(state.quickSearchHotkey));
localStorage.setItem('newEntryHotkey', JSON.stringify(state.newEntryHotkey));
if (Bridge.active) {
Bridge.setAutofillHotkeys(state.autofillEnabled, {
full: state.autofillHotkeyFull,
password: state.autofillHotkeyPwd,
quickSearch: state.quickSearchHotkey,
newEntry: state.newEntryHotkey,
});
}
}
+17 -3
View File
@@ -200,9 +200,14 @@ const Bridge = (() => {
const q = autofillComboToWin32(combos.quickSearch);
qs = '&qs_mods=' + q.mods + '&qs_vk=' + q.vk;
}
let ne = '';
if (combos.newEntry) {
const n = autofillComboToWin32(combos.newEntry);
ne = '&ne_mods=' + n.mods + '&ne_vk=' + n.vk;
}
cmd('cmd://autofill/hotkeys?enabled=' + (enabled ? '1' : '0') +
'&full_mods=' + f.mods + '&full_vk=' + f.vk +
'&pwd_mods=' + p.mods + '&pwd_vk=' + p.vk + qs);
'&pwd_mods=' + p.mods + '&pwd_vk=' + p.vk + qs + ne);
},
// Called by Delphi after a setAutofillHotkeys request, with true if
@@ -667,6 +672,8 @@ const state = {
'{"ctrl":true,"shift":true,"alt":false,"win":false,"key":"P"}'),
quickSearchHotkey: JSON.parse(localStorage.getItem('quickSearchHotkey') ||
'{"ctrl":true,"shift":true,"alt":false,"win":false,"key":"Q"}'),
newEntryHotkey: JSON.parse(localStorage.getItem('newEntryHotkey') ||
'{"ctrl":true,"shift":true,"alt":false,"win":false,"key":"A"}'),
sidebarCollapsed: JSON.parse(localStorage.getItem('sidebarCollapsed') ||
'{"folders":false,"tags":false,"tools":false}'),
// Fetch website favicons via the Delphi DuckDuckGo proxy. OFF by
@@ -6282,6 +6289,7 @@ function openSettings() {
$('#settingAutofillFullCombo').textContent = autofillComboLabel(state.autofillHotkeyFull);
$('#settingAutofillPwdCombo').textContent = autofillComboLabel(state.autofillHotkeyPwd);
$('#settingQuickSearchCombo').textContent = autofillComboLabel(state.quickSearchHotkey);
$('#settingNewEntryCombo').textContent = autofillComboLabel(state.newEntryHotkey);
$('#settingAutofillHotkeysRow').style.display = Bridge.active ? '' : 'none';
// Start-with-Windows toggle: only meaningful inside the Delphi host
// (registry access). Hide for the PHP frontend.
@@ -6668,7 +6676,7 @@ const SYNCED_SETTING_KEYS = [
// Hotkey combos are user preferences — values are portable. The
// registration itself is Windows-only, so non-Windows clients just
// ignore them.
'autofillHotkeyFull', 'autofillHotkeyPwd', 'quickSearchHotkey',
'autofillHotkeyFull', 'autofillHotkeyPwd', 'quickSearchHotkey', 'newEntryHotkey',
// Sidebar section collapsed state. Object of { folders, tags, tools }
// booleans. Synced so the user gets the same fold state across devices.
'sidebarCollapsed',
@@ -6742,6 +6750,7 @@ async function loadServerSettings() {
case 'autofillHotkeyFull':
case 'autofillHotkeyPwd':
case 'quickSearchHotkey':
case 'newEntryHotkey':
case 'sidebarCollapsed':
// Object; persist as JSON so the next cold start picks it up.
localStorage.setItem(k, JSON.stringify(v));
@@ -7706,6 +7715,7 @@ async function init() {
kind !== 'full' ? state.autofillHotkeyFull : null,
kind !== 'password' ? state.autofillHotkeyPwd : null,
kind !== 'quickSearch' ? state.quickSearchHotkey : null,
kind !== 'newEntry' ? state.newEntryHotkey : null,
].filter(Boolean);
const capJson = JSON.stringify(captured);
if (others.some(o => JSON.stringify(o) === capJson)) {
@@ -7716,7 +7726,8 @@ async function init() {
// Commit.
if (kind === 'full') state.autofillHotkeyFull = captured;
else if (kind === 'password') state.autofillHotkeyPwd = captured;
else /* quickSearch */ state.quickSearchHotkey = captured;
else if (kind === 'quickSearch') state.quickSearchHotkey = captured;
else /* newEntry */ state.newEntryHotkey = captured;
btn.textContent = autofillComboLabel(captured);
finish(false);
autofillPushHotkeys(); // re-register in Delphi
@@ -7728,14 +7739,17 @@ async function init() {
bindHotkeyCapture('#settingAutofillFullCombo', 'full');
bindHotkeyCapture('#settingAutofillPwdCombo', 'password');
bindHotkeyCapture('#settingQuickSearchCombo', 'quickSearch');
bindHotkeyCapture('#settingNewEntryCombo', 'newEntry');
$('#settingAutofillResetHotkeys').addEventListener('click', () => {
state.autofillHotkeyFull = { ctrl: true, shift: true, alt: false, win: false, key: 'L' };
state.autofillHotkeyPwd = { ctrl: true, shift: true, alt: false, win: false, key: 'P' };
state.quickSearchHotkey = { ctrl: true, shift: true, alt: false, win: false, key: 'Q' };
state.newEntryHotkey = { ctrl: true, shift: true, alt: false, win: false, key: 'A' };
$('#settingAutofillFullCombo').textContent = autofillComboLabel(state.autofillHotkeyFull);
$('#settingAutofillPwdCombo').textContent = autofillComboLabel(state.autofillHotkeyPwd);
$('#settingQuickSearchCombo').textContent = autofillComboLabel(state.quickSearchHotkey);
$('#settingNewEntryCombo').textContent = autofillComboLabel(state.newEntryHotkey);
autofillPushHotkeys();
saveServerSettings();
toast('Hotkeys reset to defaults');