feat(fillbar): onglet Fill bar dans les reglages, icones ou texte, boutons Add et Search
- boutons choisis (Full, Password, 2FA, Add current window, Search, Open), style icones (Segoe MDL2 Assets) ou texte, opacite au repos, allumee ou non - clic droit sur la barrette : Show icons / Show text en plus de l'opacite - info-bulles sur chaque bouton - Add et Search reprennent les corps extraits de Ctrl+Shift+A et Ctrl+Shift+Q Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -169,7 +169,15 @@ type
|
|||||||
// Dernier etat connu du survol, pour ne reposer l'opacite que sur un
|
// Dernier etat connu du survol, pour ne reposer l'opacite que sur un
|
||||||
// changement - SetLayeredWindowAttributes force un repaint a chaque appel.
|
// changement - SetLayeredWindowAttributes force un repaint a chaque appel.
|
||||||
FLazyHover: Boolean;
|
FLazyHover: Boolean;
|
||||||
|
// Barrette en icones (police Segoe MDL2 Assets) plutot qu'en mots, et sa
|
||||||
|
// largeur reelle - elle depend des boutons choisis dans les reglages.
|
||||||
|
FLazyIcons: Boolean;
|
||||||
|
FLazyW: Integer;
|
||||||
procedure LazyWindowHandler(var AMsg: TMessage);
|
procedure LazyWindowHandler(var AMsg: TMessage);
|
||||||
|
// Corps extraits des raccourcis Ctrl+Shift+A et Ctrl+Shift+Q : la barrette
|
||||||
|
// les declenche aussi, meme capture de la fenetre cible.
|
||||||
|
procedure FireNewEntryRequest;
|
||||||
|
procedure FireQuickSearchRequest;
|
||||||
/// Pose l'opacite qui convient a l'etat de survol donne.
|
/// Pose l'opacite qui convient a l'etat de survol donne.
|
||||||
procedure ApplyLazyAlpha(AHover: Boolean);
|
procedure ApplyLazyAlpha(AHover: Boolean);
|
||||||
/// Menu du clic droit sur la barrette : le choix de l'opacite au repos.
|
/// Menu du clic droit sur la barrette : le choix de l'opacite au repos.
|
||||||
@@ -271,6 +279,10 @@ type
|
|||||||
// peinture a besoin du theme.
|
// peinture a besoin du theme.
|
||||||
procedure RestoreLazyBar;
|
procedure RestoreLazyBar;
|
||||||
function LazyBarVisible: Boolean;
|
function LazyBarVisible: Boolean;
|
||||||
|
/// Reconstruit la barrette apres un changement de ses reglages (boutons,
|
||||||
|
/// icones). POSTE : l'appel peut venir de la barrette elle-meme, qu'on ne
|
||||||
|
/// detruit pas depuis son propre gestionnaire.
|
||||||
|
procedure ReloadLazyBar;
|
||||||
property AutofillRegistered: Boolean read FAutofillRegistered;
|
property AutofillRegistered: Boolean read FAutofillRegistered;
|
||||||
// Per-slot outcome of the last SetAutofillHotkeys — lets the host name
|
// Per-slot outcome of the last SetAutofillHotkeys — lets the host name
|
||||||
// exactly which combo lost the race with another app's global hotkey
|
// exactly which combo lost the race with another app's global hotkey
|
||||||
@@ -329,7 +341,7 @@ type
|
|||||||
implementation
|
implementation
|
||||||
|
|
||||||
uses
|
uses
|
||||||
FMX.Platform.Win;
|
FMX.Platform.Win, Winapi.CommCtrl;
|
||||||
|
|
||||||
// Foreground window at process start — captured in this unit's initialization,
|
// Foreground window at process start — captured in this unit's initialization,
|
||||||
// which runs before any of our windows exist. Every later opportunity is
|
// which runs before any of our windows exist. Every later opportunity is
|
||||||
@@ -377,6 +389,7 @@ const
|
|||||||
// traitee sur place : detruire une fenetre depuis son propre gestionnaire
|
// traitee sur place : detruire une fenetre depuis son propre gestionnaire
|
||||||
// libererait, en plein envoi, l'instance qui sert de window proc.
|
// libererait, en plein envoi, l'instance qui sert de window proc.
|
||||||
WM_LAZY_CLOSE = WM_APP + 2;
|
WM_LAZY_CLOSE = WM_APP + 2;
|
||||||
|
WM_LAZY_RELOAD = WM_APP + 3;
|
||||||
|
|
||||||
// WTS session change message and state constants (declared here to avoid
|
// WTS session change message and state constants (declared here to avoid
|
||||||
// a hard dependency on Winapi.WtsApi32 which varies across Delphi versions).
|
// a hard dependency on Winapi.WtsApi32 which varies across Delphi versions).
|
||||||
@@ -420,6 +433,9 @@ const
|
|||||||
LAZY_ID_TOTP = 103;
|
LAZY_ID_TOTP = 103;
|
||||||
LAZY_ID_CLOSE = 104;
|
LAZY_ID_CLOSE = 104;
|
||||||
LAZY_ID_OPEN = 105;
|
LAZY_ID_OPEN = 105;
|
||||||
|
LAZY_ID_ADD = 106; // nouvelle entree depuis la fenetre en avant (Ctrl+Shift+A)
|
||||||
|
LAZY_ID_SEARCH = 107; // recherche rapide (Ctrl+Shift+Q)
|
||||||
|
LAZY_MENU_ICONS = 1; // hors des valeurs d'opacite (25..100) du meme menu
|
||||||
// LE SONDAGE PLUTOT QUE TrackMouseEvent. La barrette a quatre fenetres
|
// LE SONDAGE PLUTOT QUE TrackMouseEvent. La barrette a quatre fenetres
|
||||||
// enfants : passer d'elle a un bouton envoie deja un WM_MOUSELEAVE au parent,
|
// enfants : passer d'elle a un bouton envoie deja un WM_MOUSELEAVE au parent,
|
||||||
// et il faudrait donc suivre la souris sur les cinq fenetres pour recoller
|
// et il faudrait donc suivre la souris sur les cinq fenetres pour recoller
|
||||||
@@ -438,7 +454,11 @@ const
|
|||||||
// elle doit revenir AVANT toute ouverture de coffre, et localStorage est vide
|
// elle doit revenir AVANT toute ouverture de coffre, et localStorage est vide
|
||||||
// a chaque lancement (le port ephemere change d'origine).
|
// a chaque lancement (le port ephemere change d'origine).
|
||||||
LAZY_ON_KEY = 'lazyOn';
|
LAZY_ON_KEY = 'lazyOn';
|
||||||
LAZY_W = 298;
|
// Boutons affiches, dans l'ordre, et le choix icones/texte. Le X est toujours
|
||||||
|
// la : c'est la seule facon de fermer la barrette la ou elle est.
|
||||||
|
LAZY_BTNS_KEY = 'lazyButtons';
|
||||||
|
LAZY_BTNS_DEFAULT = 'full,pwd,totp,add,search,open';
|
||||||
|
LAZY_ICONS_KEY = 'lazyIcons';
|
||||||
LAZY_H = 40;
|
LAZY_H = 40;
|
||||||
|
|
||||||
// Palette de la barrette, en COLORREF ($00BBGGRR - les octets sont INVERSES
|
// Palette de la barrette, en COLORREF ($00BBGGRR - les octets sont INVERSES
|
||||||
@@ -1142,6 +1162,9 @@ var
|
|||||||
LBrush: HBRUSH;
|
LBrush: HBRUSH;
|
||||||
LFace, LEdge, LInk: COLORREF;
|
LFace, LEdge, LInk: COLORREF;
|
||||||
LText: array[0..31] of Char;
|
LText: array[0..31] of Char;
|
||||||
|
LGlyph: Char;
|
||||||
|
LFont: HFONT;
|
||||||
|
LOldFont: HGDIOBJ;
|
||||||
begin
|
begin
|
||||||
LRect := ADraw.rcItem;
|
LRect := ADraw.rcItem;
|
||||||
if FDarkTheme then
|
if FDarkTheme then
|
||||||
@@ -1162,11 +1185,35 @@ begin
|
|||||||
FrameRect(ADraw.hDC, LRect, LBrush);
|
FrameRect(ADraw.hDC, LRect, LBrush);
|
||||||
DeleteObject(LBrush);
|
DeleteObject(LBrush);
|
||||||
|
|
||||||
|
SetBkMode(ADraw.hDC, TRANSPARENT);
|
||||||
|
SetTextColor(ADraw.hDC, LInk);
|
||||||
|
if FLazyIcons then
|
||||||
|
begin
|
||||||
|
// Segoe MDL2 Assets, livree avec Windows 10 et 11 : aucune image a
|
||||||
|
// embarquer, et le glyphe suit la couleur du texte comme un mot.
|
||||||
|
case ADraw.CtlID of
|
||||||
|
LAZY_ID_FULL: LGlyph := #$E77B; // Contact
|
||||||
|
LAZY_ID_PWD: LGlyph := #$E8D7; // Permissions (cle)
|
||||||
|
LAZY_ID_TOTP: LGlyph := #$E823; // Recent (horloge du code)
|
||||||
|
LAZY_ID_ADD: LGlyph := #$E710; // Add
|
||||||
|
LAZY_ID_SEARCH: LGlyph := #$E721; // Search
|
||||||
|
LAZY_ID_OPEN: LGlyph := #$E8A7; // OpenInNewWindow
|
||||||
|
else
|
||||||
|
LGlyph := #$E711; // Cancel (X)
|
||||||
|
end;
|
||||||
|
LFont := CreateFont(-16, 0, 0, 0, FW_NORMAL, 0, 0, 0, DEFAULT_CHARSET,
|
||||||
|
OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY,
|
||||||
|
DEFAULT_PITCH, 'Segoe MDL2 Assets');
|
||||||
|
LOldFont := SelectObject(ADraw.hDC, LFont);
|
||||||
|
DrawText(ADraw.hDC, PChar(LGlyph), 1, LRect,
|
||||||
|
DT_CENTER or DT_VCENTER or DT_SINGLELINE);
|
||||||
|
SelectObject(ADraw.hDC, LOldFont);
|
||||||
|
DeleteObject(LFont);
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
// Le libelle est celui de la fenetre-bouton : une seule verite, posee a la
|
// Le libelle est celui de la fenetre-bouton : une seule verite, posee a la
|
||||||
// creation, plutot qu'une table de correspondance a tenir a jour.
|
// creation, plutot qu'une table de correspondance a tenir a jour.
|
||||||
GetWindowText(ADraw.hwndItem, LText, Length(LText));
|
GetWindowText(ADraw.hwndItem, LText, Length(LText));
|
||||||
SetBkMode(ADraw.hDC, TRANSPARENT);
|
|
||||||
SetTextColor(ADraw.hDC, LInk);
|
|
||||||
DrawText(ADraw.hDC, LText, -1, LRect,
|
DrawText(ADraw.hDC, LText, -1, LRect,
|
||||||
DT_CENTER or DT_VCENTER or DT_SINGLELINE);
|
DT_CENTER or DT_VCENTER or DT_SINGLELINE);
|
||||||
end;
|
end;
|
||||||
@@ -1191,6 +1238,7 @@ var
|
|||||||
LPt: TPoint;
|
LPt: TPoint;
|
||||||
LCmd: Cardinal;
|
LCmd: Cardinal;
|
||||||
I: Integer;
|
I: Integer;
|
||||||
|
LIconsLabel: string;
|
||||||
begin
|
begin
|
||||||
LMenu := CreatePopupMenu;
|
LMenu := CreatePopupMenu;
|
||||||
if LMenu = 0 then Exit;
|
if LMenu = 0 then Exit;
|
||||||
@@ -1199,6 +1247,9 @@ begin
|
|||||||
AppendMenu(LMenu,
|
AppendMenu(LMenu,
|
||||||
MF_STRING or IfThen(FLazyAlphaPct = CHOICES[I], MF_CHECKED, MF_UNCHECKED),
|
MF_STRING or IfThen(FLazyAlphaPct = CHOICES[I], MF_CHECKED, MF_UNCHECKED),
|
||||||
CHOICES[I], PChar(Format('%d%% when the mouse leaves', [CHOICES[I]])));
|
CHOICES[I], PChar(Format('%d%% when the mouse leaves', [CHOICES[I]])));
|
||||||
|
if FLazyIcons then LIconsLabel := 'Show text' else LIconsLabel := 'Show icons';
|
||||||
|
AppendMenu(LMenu, MF_SEPARATOR, 0, nil);
|
||||||
|
AppendMenu(LMenu, MF_STRING, LAZY_MENU_ICONS, PChar(LIconsLabel));
|
||||||
|
|
||||||
// LE BATTEMENT DOIT S'ARRETER PENDANT LE MENU. Il repose la barrette en
|
// LE BATTEMENT DOIT S'ARRETER PENDANT LE MENU. Il repose la barrette en
|
||||||
// tete de la bande TOPMOST cinq fois par seconde - or le menu deroulant est
|
// tete de la bande TOPMOST cinq fois par seconde - or le menu deroulant est
|
||||||
@@ -1216,7 +1267,12 @@ begin
|
|||||||
LPt.X, LPt.Y, 0, FMsgWindow, nil));
|
LPt.X, LPt.Y, 0, FMsgWindow, nil));
|
||||||
PostMessage(FMsgWindow, WM_NULL, 0, 0);
|
PostMessage(FMsgWindow, WM_NULL, 0, 0);
|
||||||
|
|
||||||
if LCmd > 0 then
|
if LCmd = LAZY_MENU_ICONS then
|
||||||
|
begin
|
||||||
|
if FLazyIcons then SetPref(LAZY_ICONS_KEY, '0') else SetPref(LAZY_ICONS_KEY, '1');
|
||||||
|
ReloadLazyBar;
|
||||||
|
end
|
||||||
|
else if LCmd > 0 then
|
||||||
begin
|
begin
|
||||||
FLazyAlphaPct := LCmd;
|
FLazyAlphaPct := LCmd;
|
||||||
SetPref(LAZY_ALPHA_KEY, IntToStr(FLazyAlphaPct));
|
SetPref(LAZY_ALPHA_KEY, IntToStr(FLazyAlphaPct));
|
||||||
@@ -1317,6 +1373,8 @@ begin
|
|||||||
LAZY_ID_PWD: FireAutofillRequest(akPasswordOnly, True);
|
LAZY_ID_PWD: FireAutofillRequest(akPasswordOnly, True);
|
||||||
LAZY_ID_TOTP: FireAutofillRequest(akTotp, True);
|
LAZY_ID_TOTP: FireAutofillRequest(akTotp, True);
|
||||||
LAZY_ID_OPEN: if Assigned(FOnTrayRestore) then FOnTrayRestore();
|
LAZY_ID_OPEN: if Assigned(FOnTrayRestore) then FOnTrayRestore();
|
||||||
|
LAZY_ID_ADD: FireNewEntryRequest;
|
||||||
|
LAZY_ID_SEARCH: FireQuickSearchRequest;
|
||||||
LAZY_ID_CLOSE: PostMessage(FMsgWindow, WM_LAZY_CLOSE, 0, 0);
|
LAZY_ID_CLOSE: PostMessage(FMsgWindow, WM_LAZY_CLOSE, 0, 0);
|
||||||
end;
|
end;
|
||||||
AMsg.Result := 0;
|
AMsg.Result := 0;
|
||||||
@@ -1326,20 +1384,84 @@ begin
|
|||||||
AMsg.Result := DefWindowProc(FLazyWnd, AMsg.Msg, AMsg.WParam, AMsg.LParam);
|
AMsg.Result := DefWindowProc(FLazyWnd, AMsg.Msg, AMsg.WParam, AMsg.LParam);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TPMBridge.FireNewEntryRequest;
|
||||||
|
var
|
||||||
|
LTarget: HWND;
|
||||||
|
LTitle: string;
|
||||||
|
LLen: Integer;
|
||||||
|
begin
|
||||||
|
if not Assigned(FOnNewEntryHotkey) then Exit;
|
||||||
|
LTarget := GetForegroundWindow;
|
||||||
|
LTitle := '';
|
||||||
|
// The window we are about to cover is exactly the one to hand the
|
||||||
|
// foreground back to when we return to the tray.
|
||||||
|
CaptureForegroundOwner;
|
||||||
|
// From the tray the foreground can resolve to OUR own window — don't
|
||||||
|
// pre-fill the new entry with "Password Manager". Only capture a real
|
||||||
|
// external window's title; otherwise pass '' (blank title).
|
||||||
|
if not IsOwnProcessWindow(LTarget) then
|
||||||
|
begin
|
||||||
|
SetLength(LTitle, 512);
|
||||||
|
LLen := GetWindowTextW(LTarget, PChar(LTitle), 512);
|
||||||
|
SetLength(LTitle, LLen);
|
||||||
|
end;
|
||||||
|
FOnNewEntryHotkey(LTitle);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TPMBridge.FireQuickSearchRequest;
|
||||||
|
var
|
||||||
|
LTarget: HWND;
|
||||||
|
LTitle: string;
|
||||||
|
LLen: Integer;
|
||||||
|
begin
|
||||||
|
// Quick-search + autofill: capture the foreground HWND BEFORE the JS
|
||||||
|
// modal steals focus, hand it to the host so it can stash it in
|
||||||
|
// FAutofillTargetHWND (consumed by cmd://autofill/execute later).
|
||||||
|
if not Assigned(FOnQuickSearchHotkey) then Exit;
|
||||||
|
LTarget := GetForegroundWindow;
|
||||||
|
CaptureForegroundOwner;
|
||||||
|
// Same trap as the autofill hotkeys: our own hidden window can be the
|
||||||
|
// foreground one. Report no target — the host then opens quick search in
|
||||||
|
// copy mode instead of offering to type into ourselves.
|
||||||
|
if IsOwnProcessWindow(LTarget) then LTarget := 0;
|
||||||
|
LTitle := '';
|
||||||
|
if LTarget <> 0 then
|
||||||
|
begin
|
||||||
|
SetLength(LTitle, 512);
|
||||||
|
LLen := GetWindowTextW(LTarget, PChar(LTitle), 512);
|
||||||
|
SetLength(LTitle, LLen);
|
||||||
|
end;
|
||||||
|
FOnQuickSearchHotkey(akPasswordOnly, LTarget, LTitle);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TPMBridge.ReloadLazyBar;
|
||||||
|
begin
|
||||||
|
PostMessage(FMsgWindow, WM_LAZY_RELOAD, 0, 0);
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TPMBridge.RestoreLazyBar;
|
procedure TPMBridge.RestoreLazyBar;
|
||||||
begin
|
begin
|
||||||
if GetPref(LAZY_ON_KEY) = '1' then SetLazyBar(True);
|
if GetPref(LAZY_ON_KEY) = '1' then SetLazyBar(True);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TPMBridge.SetLazyBar(AOn: Boolean);
|
procedure TPMBridge.SetLazyBar(AOn: Boolean);
|
||||||
|
// Declarees AVANT les routines imbriquees, qui s'en servent : une routine
|
||||||
|
// imbriquee ne voit que les variables declarees au-dessus d'elle.
|
||||||
|
var
|
||||||
|
LWork, LRect: TRect;
|
||||||
|
LSaved: TArray<string>;
|
||||||
|
LX, LY, LPosX, LPosY: Integer;
|
||||||
|
LList: string;
|
||||||
|
LTip: HWND;
|
||||||
|
|
||||||
// BS_OWNERDRAW : le systeme garde le suivi de la souris et l'etat enfonce,
|
// BS_OWNERDRAW : le systeme garde le suivi de la souris et l'etat enfonce,
|
||||||
// nous ne reprenons que les couleurs. Un bouton standard resterait gris clair
|
// nous ne reprenons que les couleurs. Un bouton standard resterait gris clair
|
||||||
// sur fond sombre, comme sur la capture qui a motive ce changement.
|
// sur fond sombre, comme sur la capture qui a motive ce changement.
|
||||||
procedure MakeButton(AId: Integer; const ACaption: string;
|
procedure MakeButton(AId: Integer; const ACaption, ATip: string;
|
||||||
AX, AWidth: Integer);
|
AX, AWidth: Integer);
|
||||||
var
|
var
|
||||||
LBtn: HWND;
|
LBtn: HWND;
|
||||||
|
LTool: TToolInfo;
|
||||||
begin
|
begin
|
||||||
LBtn := CreateWindow('BUTTON', PChar(ACaption),
|
LBtn := CreateWindow('BUTTON', PChar(ACaption),
|
||||||
WS_CHILD or WS_VISIBLE or BS_OWNERDRAW, AX, 5, AWidth, 28,
|
WS_CHILD or WS_VISIBLE or BS_OWNERDRAW, AX, 5, AWidth, 28,
|
||||||
@@ -1349,12 +1471,32 @@ procedure TPMBridge.SetLazyBar(AOn: Boolean);
|
|||||||
SendMessage(LBtn, WM_SETFONT, WPARAM(GetStockObject(DEFAULT_GUI_FONT)), 1);
|
SendMessage(LBtn, WM_SETFONT, WPARAM(GetStockObject(DEFAULT_GUI_FONT)), 1);
|
||||||
GLazyOldBtnProc := Pointer(SetWindowLongPtr(LBtn, GWLP_WNDPROC,
|
GLazyOldBtnProc := Pointer(SetWindowLongPtr(LBtn, GWLP_WNDPROC,
|
||||||
LONG_PTR(@LazyButtonProc)));
|
LONG_PTR(@LazyButtonProc)));
|
||||||
|
// UNE ICONE SANS MOT SE NOMME AU SURVOL. Info-bulle posee dans les deux
|
||||||
|
// modes : elle dit aussi ce que fait "Add" ou "Search" en texte.
|
||||||
|
if LTip <> 0 then
|
||||||
|
begin
|
||||||
|
FillChar(LTool, SizeOf(LTool), 0);
|
||||||
|
LTool.cbSize := SizeOf(LTool);
|
||||||
|
LTool.uFlags := TTF_IDISHWND or TTF_SUBCLASS;
|
||||||
|
LTool.hwnd := FLazyWnd;
|
||||||
|
LTool.uId := UINT_PTR(LBtn);
|
||||||
|
LTool.lpszText := PChar(ATip);
|
||||||
|
SendMessage(LTip, TTM_ADDTOOL, 0, LPARAM(@LTool));
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// Un bouton du jeu choisi dans les reglages, pose a la suite des precedents.
|
||||||
|
procedure AddButton(const AKey: string; AId: Integer;
|
||||||
|
const ACaption, ATip: string; ATextWidth: Integer);
|
||||||
|
var
|
||||||
|
LW: Integer;
|
||||||
|
begin
|
||||||
|
if not (',' + LList + ',').Contains(',' + AKey + ',') then Exit;
|
||||||
|
LW := IfThen(FLazyIcons, 30, ATextWidth);
|
||||||
|
MakeButton(AId, ACaption, ATip, LX, LW);
|
||||||
|
Inc(LX, LW + 6);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
var
|
|
||||||
LWork, LRect: TRect;
|
|
||||||
LSaved: TArray<string>;
|
|
||||||
LX, LY, LPosX, LPosY: Integer;
|
|
||||||
begin
|
begin
|
||||||
if AOn = LazyBarVisible then Exit;
|
if AOn = LazyBarVisible then Exit;
|
||||||
if AOn then SetPref(LAZY_ON_KEY, '1') else SetPref(LAZY_ON_KEY, '0');
|
if AOn then SetPref(LAZY_ON_KEY, '1') else SetPref(LAZY_ON_KEY, '0');
|
||||||
@@ -1376,23 +1518,37 @@ begin
|
|||||||
if (FLazyAlphaPct < 25) or (FLazyAlphaPct > 100) then
|
if (FLazyAlphaPct < 25) or (FLazyAlphaPct > 100) then
|
||||||
FLazyAlphaPct := LAZY_ALPHA_DEFAULT;
|
FLazyAlphaPct := LAZY_ALPHA_DEFAULT;
|
||||||
|
|
||||||
MakeButton(LAZY_ID_FULL, 'Full', 5, 56);
|
FLazyIcons := GetPref(LAZY_ICONS_KEY) = '1';
|
||||||
MakeButton(LAZY_ID_PWD, 'Password', 67, 76);
|
LList := GetPref(LAZY_BTNS_KEY);
|
||||||
MakeButton(LAZY_ID_TOTP, '2FA', 149, 52);
|
if LList = '' then LList := LAZY_BTNS_DEFAULT;
|
||||||
|
InitCommonControls;
|
||||||
|
LTip := CreateWindowEx(WS_EX_TOPMOST, TOOLTIPS_CLASS, nil,
|
||||||
|
WS_POPUP or TTS_ALWAYSTIP or TTS_NOPREFIX,
|
||||||
|
Integer(CW_USEDEFAULT), Integer(CW_USEDEFAULT),
|
||||||
|
Integer(CW_USEDEFAULT), Integer(CW_USEDEFAULT),
|
||||||
|
FLazyWnd, 0, HInstance, nil);
|
||||||
|
|
||||||
|
LX := 5;
|
||||||
|
AddButton('full', LAZY_ID_FULL, 'Full', 'Fill username and password', 56);
|
||||||
|
AddButton('pwd', LAZY_ID_PWD, 'Password', 'Fill the password only', 76);
|
||||||
|
AddButton('totp', LAZY_ID_TOTP, '2FA', 'Fill the 2FA code', 52);
|
||||||
|
AddButton('add', LAZY_ID_ADD, 'Add', 'New entry from the current window', 48);
|
||||||
|
AddButton('search', LAZY_ID_SEARCH, 'Search', 'Quick search', 60);
|
||||||
// Meme raison que le X : quand la barrette est allumee, elle est souvent la
|
// Meme raison que le X : quand la barrette est allumee, elle est souvent la
|
||||||
// seule chose de l'application a l'ecran. Revenir au coffre ne doit pas
|
// seule chose de l'application a l'ecran. Revenir au coffre ne doit pas
|
||||||
// demander de retrouver l'icone du tray.
|
// demander de retrouver l'icone du tray.
|
||||||
MakeButton(LAZY_ID_OPEN, 'Open', 207, 56);
|
AddButton('open', LAZY_ID_OPEN, 'Open', 'Open the vault', 56);
|
||||||
// Fermer la barrette sans passer par le menu du tray : elle flotte au-dessus
|
// Fermer la barrette sans passer par le menu du tray : elle flotte au-dessus
|
||||||
// de tout, donc s'en debarrasser doit se faire la ou elle est.
|
// de tout, donc s'en debarrasser doit se faire la ou elle est.
|
||||||
MakeButton(LAZY_ID_CLOSE, 'X', 269, 24);
|
MakeButton(LAZY_ID_CLOSE, 'X', 'Close the fill bar', LX, 24);
|
||||||
|
FLazyW := LX + 24 + 5;
|
||||||
|
|
||||||
// Bottom right of the WORK area, so it does not start life under the
|
// Bottom right of the WORK area, so it does not start life under the
|
||||||
// taskbar. From there the user drags it wherever the login form is.
|
// taskbar. From there the user drags it wherever the login form is.
|
||||||
if not SystemParametersInfo(SPI_GETWORKAREA, 0, @LWork, 0) then
|
if not SystemParametersInfo(SPI_GETWORKAREA, 0, @LWork, 0) then
|
||||||
LWork := Rect(0, 0, GetSystemMetrics(SM_CXSCREEN),
|
LWork := Rect(0, 0, GetSystemMetrics(SM_CXSCREEN),
|
||||||
GetSystemMetrics(SM_CYSCREEN));
|
GetSystemMetrics(SM_CYSCREEN));
|
||||||
LX := LWork.Right - LAZY_W - 24;
|
LX := LWork.Right - FLazyW - 24;
|
||||||
LY := LWork.Bottom - LAZY_H - 24;
|
LY := LWork.Bottom - LAZY_H - 24;
|
||||||
|
|
||||||
// LA POSITION RETENUE, SI ELLE TOMBE ENCORE SUR UN ECRAN. Un ecran debranche
|
// LA POSITION RETENUE, SI ELLE TOMBE ENCORE SUR UN ECRAN. Un ecran debranche
|
||||||
@@ -1403,7 +1559,7 @@ begin
|
|||||||
if (Length(LSaved) = 2) and TryStrToInt(LSaved[0], LPosX)
|
if (Length(LSaved) = 2) and TryStrToInt(LSaved[0], LPosX)
|
||||||
and TryStrToInt(LSaved[1], LPosY) then
|
and TryStrToInt(LSaved[1], LPosY) then
|
||||||
begin
|
begin
|
||||||
LRect := Rect(LPosX, LPosY, LPosX + LAZY_W, LPosY + LAZY_H);
|
LRect := Rect(LPosX, LPosY, LPosX + FLazyW, LPosY + LAZY_H);
|
||||||
if MonitorFromRect(@LRect, MONITOR_DEFAULTTONULL) <> 0 then
|
if MonitorFromRect(@LRect, MONITOR_DEFAULTTONULL) <> 0 then
|
||||||
begin
|
begin
|
||||||
LX := LPosX;
|
LX := LPosX;
|
||||||
@@ -1411,7 +1567,7 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
SetWindowPos(FLazyWnd, HWND_TOPMOST, LX, LY, LAZY_W, LAZY_H,
|
SetWindowPos(FLazyWnd, HWND_TOPMOST, LX, LY, FLazyW, LAZY_H,
|
||||||
SWP_NOACTIVATE or SWP_SHOWWINDOW);
|
SWP_NOACTIVATE or SWP_SHOWWINDOW);
|
||||||
|
|
||||||
// La souris est ailleurs a la seconde ou la barrette parait : elle nait donc
|
// La souris est ailleurs a la seconde ou la barrette parait : elle nait donc
|
||||||
@@ -1435,6 +1591,16 @@ begin
|
|||||||
Exit;
|
Exit;
|
||||||
end
|
end
|
||||||
|
|
||||||
|
else if AMsg.Msg = WM_LAZY_RELOAD then
|
||||||
|
begin
|
||||||
|
if LazyBarVisible then
|
||||||
|
begin
|
||||||
|
SetLazyBar(False);
|
||||||
|
SetLazyBar(True);
|
||||||
|
end;
|
||||||
|
Exit;
|
||||||
|
end
|
||||||
|
|
||||||
else if AMsg.Msg = WM_TRAY_ICON then
|
else if AMsg.Msg = WM_TRAY_ICON then
|
||||||
begin
|
begin
|
||||||
// For Shell_NotifyIcon callback messages, the mouse event is in the
|
// For Shell_NotifyIcon callback messages, the mouse event is in the
|
||||||
@@ -1495,50 +1661,10 @@ begin
|
|||||||
end
|
end
|
||||||
|
|
||||||
else if (AMsg.Msg = WM_HOTKEY) and (AMsg.WParam = NEW_ENTRY_HOTKEY_ID) then
|
else if (AMsg.Msg = WM_HOTKEY) and (AMsg.WParam = NEW_ENTRY_HOTKEY_ID) then
|
||||||
begin
|
FireNewEntryRequest
|
||||||
if Assigned(FOnNewEntryHotkey) then
|
|
||||||
begin
|
|
||||||
var LTarget := GetForegroundWindow;
|
|
||||||
var LTitle: string := '';
|
|
||||||
// The window we are about to cover is exactly the one to hand the
|
|
||||||
// foreground back to when we return to the tray.
|
|
||||||
CaptureForegroundOwner;
|
|
||||||
// From the tray the foreground can resolve to OUR own window — don't
|
|
||||||
// pre-fill the new entry with "Password Manager". Only capture a real
|
|
||||||
// external window's title; otherwise pass '' (blank title).
|
|
||||||
if not IsOwnProcessWindow(LTarget) then
|
|
||||||
begin
|
|
||||||
SetLength(LTitle, 512);
|
|
||||||
var LLen := GetWindowTextW(LTarget, PChar(LTitle), 512);
|
|
||||||
SetLength(LTitle, LLen);
|
|
||||||
end;
|
|
||||||
FOnNewEntryHotkey(LTitle);
|
|
||||||
end;
|
|
||||||
end
|
|
||||||
|
|
||||||
else if (AMsg.Msg = WM_HOTKEY) and (AMsg.WParam = QUICK_SEARCH_HOTKEY_ID) then
|
else if (AMsg.Msg = WM_HOTKEY) and (AMsg.WParam = QUICK_SEARCH_HOTKEY_ID) then
|
||||||
begin
|
FireQuickSearchRequest
|
||||||
// Quick-search + autofill: capture the foreground HWND BEFORE the JS
|
|
||||||
// modal steals focus, hand it to the host so it can stash it in
|
|
||||||
// FAutofillTargetHWND (consumed by cmd://autofill/execute later).
|
|
||||||
if Assigned(FOnQuickSearchHotkey) then
|
|
||||||
begin
|
|
||||||
var LTarget := GetForegroundWindow;
|
|
||||||
CaptureForegroundOwner;
|
|
||||||
// Same trap as the autofill hotkeys: our own hidden window can be the
|
|
||||||
// foreground one. Report no target — the host then opens quick search in
|
|
||||||
// copy mode instead of offering to type into ourselves.
|
|
||||||
if IsOwnProcessWindow(LTarget) then LTarget := 0;
|
|
||||||
var LTitle: string;
|
|
||||||
if LTarget <> 0 then
|
|
||||||
begin
|
|
||||||
SetLength(LTitle, 512);
|
|
||||||
var LLen := GetWindowTextW(LTarget, PChar(LTitle), 512);
|
|
||||||
SetLength(LTitle, LLen);
|
|
||||||
end;
|
|
||||||
FOnQuickSearchHotkey(akPasswordOnly, LTarget, LTitle);
|
|
||||||
end;
|
|
||||||
end
|
|
||||||
|
|
||||||
else if (AMsg.Msg = WM_HOTKEY) and Assigned(FOnAutofillRequest) and
|
else if (AMsg.Msg = WM_HOTKEY) and Assigned(FOnAutofillRequest) and
|
||||||
((AMsg.WParam = AUTOFILL_HOTKEY_ID_FULL) or
|
((AMsg.WParam = AUTOFILL_HOTKEY_ID_FULL) or
|
||||||
|
|||||||
@@ -1323,6 +1323,42 @@ begin
|
|||||||
'Bridge.onPrefResult("' + LEsc + '#has","' + LSet + '")');
|
'Bridge.onPrefResult("' + LEsc + '#has","' + LSet + '")');
|
||||||
end
|
end
|
||||||
|
|
||||||
|
// ---- Barrette (Fill bar) : reglages depuis Settings -> Fill bar ---------
|
||||||
|
// Rien de secret ici : quels boutons, icones ou mots, opacite, allumee.
|
||||||
|
else if ACmd = 'fillbar/get' then
|
||||||
|
begin
|
||||||
|
var LFb := TJSONObject.Create;
|
||||||
|
try
|
||||||
|
LFb.AddPair('on', TJSONBool.Create(FBridge.LazyBarVisible));
|
||||||
|
LFb.AddPair('buttons', PM.UserPrefs.GetPref('lazyButtons'));
|
||||||
|
LFb.AddPair('icons', TJSONBool.Create(PM.UserPrefs.GetPref('lazyIcons') = '1'));
|
||||||
|
LFb.AddPair('alpha', PM.UserPrefs.GetPref('lazyAlphaPct'));
|
||||||
|
WebBrowser.ExecuteJavaScript(
|
||||||
|
'if(window.Bridge&&Bridge.onFillBar)Bridge.onFillBar(' + LFb.ToJSON + ')');
|
||||||
|
finally
|
||||||
|
LFb.Free;
|
||||||
|
end;
|
||||||
|
end
|
||||||
|
|
||||||
|
else if ACmd = 'fillbar/set' then
|
||||||
|
begin
|
||||||
|
PM.UserPrefs.SetPref('lazyButtons', GetParam('buttons'));
|
||||||
|
if GetParam('icons') = '1' then PM.UserPrefs.SetPref('lazyIcons', '1')
|
||||||
|
else PM.UserPrefs.SetPref('lazyIcons', '0');
|
||||||
|
var LAlpha := StrToIntDef(GetParam('alpha'), 60);
|
||||||
|
if (LAlpha >= 25) and (LAlpha <= 100) then
|
||||||
|
PM.UserPrefs.SetPref('lazyAlphaPct', IntToStr(LAlpha));
|
||||||
|
LogLine('fillbar/set: ' + GetParam('buttons') + ' icons=' + GetParam('icons') +
|
||||||
|
' alpha=' + GetParam('alpha') + ' on=' + GetParam('on'));
|
||||||
|
if GetParam('on') = '1' then
|
||||||
|
begin
|
||||||
|
if FBridge.LazyBarVisible then FBridge.ReloadLazyBar
|
||||||
|
else FBridge.SetLazyBar(True);
|
||||||
|
end
|
||||||
|
else
|
||||||
|
FBridge.SetLazyBar(False);
|
||||||
|
end
|
||||||
|
|
||||||
else if ACmd = 'prefs/set' then
|
else if ACmd = 'prefs/set' then
|
||||||
begin
|
begin
|
||||||
var LKey := GetParam('key');
|
var LKey := GetParam('key');
|
||||||
|
|||||||
+48
@@ -444,6 +444,7 @@
|
|||||||
<button class="settings-tab is-active" data-tab="general"><svg><use href="#i-settings"/></svg>General</button>
|
<button class="settings-tab is-active" data-tab="general"><svg><use href="#i-settings"/></svg>General</button>
|
||||||
<button class="settings-tab" data-tab="security"><svg><use href="#i-shield"/></svg>Security</button>
|
<button class="settings-tab" data-tab="security"><svg><use href="#i-shield"/></svg>Security</button>
|
||||||
<button class="settings-tab" data-tab="autofill"><svg><use href="#i-key"/></svg>Autofill</button>
|
<button class="settings-tab" data-tab="autofill"><svg><use href="#i-key"/></svg>Autofill</button>
|
||||||
|
<button class="settings-tab" data-tab="fillbar" id="settingsTabFillbar"><svg><use href="#i-grid"/></svg>Fill bar</button>
|
||||||
<button class="settings-tab" data-tab="account"><svg><use href="#i-user"/></svg>Account</button>
|
<button class="settings-tab" data-tab="account"><svg><use href="#i-user"/></svg>Account</button>
|
||||||
<button class="settings-tab" data-tab="sync"><svg><use href="#i-rotate-ccw"/></svg>Sync & Backup</button>
|
<button class="settings-tab" data-tab="sync"><svg><use href="#i-rotate-ccw"/></svg>Sync & Backup</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -738,6 +739,53 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="slideover-field" id="fillbarSection">
|
||||||
|
<div class="slideover-field-label">Fill bar</div>
|
||||||
|
<p style="font-size:12px;color:var(--text-dim);margin:0 0 8px;line-height:1.5">
|
||||||
|
A small floating bar that does with the mouse what the hotkeys
|
||||||
|
do from the keyboard. Right-click it for the same opacity and
|
||||||
|
icons/text choices.
|
||||||
|
</p>
|
||||||
|
<div class="setting-row">
|
||||||
|
<span>Show the fill bar</span>
|
||||||
|
<label class="toggle">
|
||||||
|
<input type="checkbox" id="fbOn">
|
||||||
|
<span class="toggle-slider"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="setting-row">
|
||||||
|
<span>Buttons</span>
|
||||||
|
<span style="display:flex;flex-wrap:wrap;gap:4px 12px;justify-content:flex-end">
|
||||||
|
<label><input type="checkbox" class="fb-btn" data-key="full"> Full</label>
|
||||||
|
<label><input type="checkbox" class="fb-btn" data-key="pwd"> Password</label>
|
||||||
|
<label><input type="checkbox" class="fb-btn" data-key="totp"> 2FA</label>
|
||||||
|
<label><input type="checkbox" class="fb-btn" data-key="add"> Add current window</label>
|
||||||
|
<label><input type="checkbox" class="fb-btn" data-key="search"> Search</label>
|
||||||
|
<label><input type="checkbox" class="fb-btn" data-key="open"> Open</label>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="setting-row">
|
||||||
|
<span>Style</span>
|
||||||
|
<select id="fbStyle">
|
||||||
|
<option value="text">Text</option>
|
||||||
|
<option value="icons">Icons</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="setting-row">
|
||||||
|
<span>
|
||||||
|
Opacity when the mouse leaves
|
||||||
|
<small class="setting-hint">Full again as soon as the mouse is over it.</small>
|
||||||
|
</span>
|
||||||
|
<select id="fbAlpha">
|
||||||
|
<option value="100">100%</option>
|
||||||
|
<option value="75">75%</option>
|
||||||
|
<option value="60">60% (default)</option>
|
||||||
|
<option value="50">50%</option>
|
||||||
|
<option value="25">25%</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="slideover-field">
|
<div class="slideover-field">
|
||||||
<div class="slideover-field-label">Clipboard privacy</div>
|
<div class="slideover-field-label">Clipboard privacy</div>
|
||||||
<p style="font-size:12px;color:var(--text-dim);margin:0 0 8px;line-height:1.5">
|
<p style="font-size:12px;color:var(--text-dim);margin:0 0 8px;line-height:1.5">
|
||||||
|
|||||||
@@ -6696,7 +6696,33 @@ function revealSyncSettings() {
|
|||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BARRETTE (Fill bar). Ses reglages vivent en prefs DPAPI cote Delphi, pas dans
|
||||||
|
// settings_json : elle s'allume coffre ferme, sans session. La page ne fait que
|
||||||
|
// les lire (fillbar/get) et les renvoyer tous ensemble a chaque changement.
|
||||||
|
function fillBarRequest() {
|
||||||
|
if (Bridge.active) window.location.href = 'cmd://fillbar/get';
|
||||||
|
}
|
||||||
|
Bridge.onFillBar = function (st) {
|
||||||
|
const keys = (st.buttons || 'full,pwd,totp,add,search,open').split(',');
|
||||||
|
$('#fbOn').checked = !!st.on;
|
||||||
|
$$('.fb-btn').forEach(c => { c.checked = keys.includes(c.dataset.key); });
|
||||||
|
$('#fbStyle').value = st.icons ? 'icons' : 'text';
|
||||||
|
$('#fbAlpha').value = String(parseInt(st.alpha, 10) || 60);
|
||||||
|
};
|
||||||
|
function fillBarPush() {
|
||||||
|
const keys = Array.from($$('.fb-btn')).filter(c => c.checked).map(c => c.dataset.key);
|
||||||
|
window.location.href = 'cmd://fillbar/set'
|
||||||
|
+ '?on=' + ($('#fbOn').checked ? '1' : '0')
|
||||||
|
+ '&buttons=' + encodeURIComponent(keys.join(',') || 'none')
|
||||||
|
+ '&icons=' + ($('#fbStyle').value === 'icons' ? '1' : '0')
|
||||||
|
+ '&alpha=' + encodeURIComponent($('#fbAlpha').value);
|
||||||
|
}
|
||||||
|
|
||||||
function openSettings() {
|
function openSettings() {
|
||||||
|
// Hors de l'exe (navigateur de dev) il n'y a pas de barrette.
|
||||||
|
$('#fillbarSection').style.display = Bridge.active ? '' : 'none';
|
||||||
|
$('#settingsTabFillbar').style.display = Bridge.active ? '' : 'none';
|
||||||
|
fillBarRequest();
|
||||||
$('#settingTheme').value = state.theme;
|
$('#settingTheme').value = state.theme;
|
||||||
$('#settingSort').value = state.sortBy + ':' + state.sortDir;
|
$('#settingSort').value = state.sortBy + ':' + state.sortDir;
|
||||||
$('#settingAutoLock').value = String(state.autoLock);
|
$('#settingAutoLock').value = String(state.autoLock);
|
||||||
@@ -6819,6 +6845,7 @@ const SETTINGS_TAB_OF = {
|
|||||||
'Appearance': 'general',
|
'Appearance': 'general',
|
||||||
'Security': 'security', 'Clipboard privacy': 'security',
|
'Security': 'security', 'Clipboard privacy': 'security',
|
||||||
'Autofill': 'autofill',
|
'Autofill': 'autofill',
|
||||||
|
'Fill bar': 'fillbar',
|
||||||
'Account': 'account',
|
'Account': 'account',
|
||||||
'PIN unlock': 'account', 'Quick unlock': 'account', 'Recovery key': 'account',
|
'PIN unlock': 'account', 'Quick unlock': 'account', 'Recovery key': 'account',
|
||||||
'Export': 'sync', 'Sync (WebDAV or Gitea)': 'sync',
|
'Export': 'sync', 'Sync (WebDAV or Gitea)': 'sync',
|
||||||
@@ -8193,6 +8220,11 @@ async function init() {
|
|||||||
if (syncTest) syncTest.addEventListener('click', syncTestConnection);
|
if (syncTest) syncTest.addEventListener('click', syncTestConnection);
|
||||||
const syncQr = document.getElementById('syncQrBtn');
|
const syncQr = document.getElementById('syncQrBtn');
|
||||||
if (syncQr) syncQr.addEventListener('click', syncQrToggle);
|
if (syncQr) syncQr.addEventListener('click', syncQrToggle);
|
||||||
|
['#fbOn', '#fbStyle', '#fbAlpha'].forEach(sel => {
|
||||||
|
const el = $(sel);
|
||||||
|
if (el) el.addEventListener('change', fillBarPush);
|
||||||
|
});
|
||||||
|
$$('.fb-btn').forEach(c => c.addEventListener('change', fillBarPush));
|
||||||
// Guard the user entry point: ignore clicks while a sync is running (no
|
// Guard the user entry point: ignore clicks while a sync is running (no
|
||||||
// concurrent syncs) and call runSyncNow() with NO arg — the click event
|
// concurrent syncs) and call runSyncNow() with NO arg — the click event
|
||||||
// must not land in its `_attempt` retry counter. Internal retries call
|
// must not land in its `_attempt` retry counter. Internal retries call
|
||||||
|
|||||||
Reference in New Issue
Block a user