feat(autofill): un troisieme bouton 2FA, et la barrette suit le theme

SetPreferredAppMode(ForceDark) ne themise que ce que le systeme dessine
lui-meme - menus, ascenseurs, infobulles. Un bouton Win32 standard reste
donc gris clair sur fond sombre, ce que la capture montrait.

BS_OWNERDRAW + WM_DRAWITEM : le systeme garde le suivi de la souris et
l'etat enfonce, nous ne reprenons que les couleurs, recopiees des
variables de css/style.css. Le cadre passe dans WM_ERASEBKGND plutot que
par WS_BORDER, qui se sert dans les couleurs systeme.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
r-zakarya
2026-08-26 15:49:49 +01:00
parent 09bbd3fe9e
commit 52f5024de0
2 changed files with 104 additions and 12 deletions
+11 -3
View File
@@ -162,9 +162,9 @@ via `settings_json`).
## Lazy mode (remplir à la souris) ## Lazy mode (remplir à la souris)
Menu du tray → **Lazy mode (fill with the mouse)** : une barrette de deux Menu du tray → **Lazy mode (fill with the mouse)** : une barrette de trois
boutons *Full* / *Password* qui flotte au-dessus de tout et fait exactement ce boutons *Full* / *Password* / *2FA* qui flotte au-dessus de tout et fait
que font `Ctrl+Shift+L` / `Ctrl+Shift+P`. exactement ce que font `Ctrl+Shift+L` / `Ctrl+Shift+P` / `Ctrl+Shift+T`.
**`WS_EX_NOACTIVATE` est toute l'astuce.** Une fenêtre ordinaire prendrait le **`WS_EX_NOACTIVATE` est toute l'astuce.** Une fenêtre ordinaire prendrait le
premier plan au clic — le champ que l'utilisateur vient de cliquer perdrait son premier plan au clic — le champ que l'utilisateur vient de cliquer perdrait son
@@ -185,6 +185,14 @@ Détails :
pousser). `WM_ERASEBKGND` peint le fond — la classe `TPUtilWindow` pousser). `WM_ERASEBKGND` peint le fond — la classe `TPUtilWindow`
d'`AllocateHWnd` n'a **pas** de brosse de fond. d'`AllocateHWnd` n'a **pas** de brosse de fond.
- Position : coin bas-droit de la *work area* au démarrage, **pas persistée**. - Position : coin bas-droit de la *work area* au démarrage, **pas persistée**.
- **Thème** : boutons `BS_OWNERDRAW` + `WM_DRAWITEM`, palette recopiée des
variables de `css/style.css` en COLORREF (`$00BBGGRR` — octets **inversés**
par rapport à l'hexa CSS). `SetPreferredAppMode(ForceDark)` ne themise que ce
que le système dessine lui-même (menus, ascenseurs, infobulles) : un bouton
Win32 standard reste gris clair sur fond sombre. `ApplyTitleBarTheme` mémorise
le mode dans `FDarkTheme` et `RedrawWindow(… RDW_ALLCHILDREN)` la barrette.
Le cadre est peint dans `WM_ERASEBKGND` plutôt que par `WS_BORDER`, qui se
sert dans les couleurs système.
## Debug mode ## Debug mode
+93 -9
View File
@@ -29,7 +29,7 @@ unit PM.Bridge;
interface interface
uses uses
System.SysUtils, System.Classes, System.Math, System.SysUtils, System.Classes, System.Math, System.Types,
FMX.Types, FMX.Forms, FMX.Types, FMX.Forms,
Winapi.Windows, Winapi.ShellAPI, Winapi.Messages, Winapi.Windows, Winapi.ShellAPI, Winapi.Messages,
PM.SingleInstance; PM.SingleInstance;
@@ -152,7 +152,15 @@ type
// foreground: the browser stays active, its field stays focused, and the // foreground: the browser stays active, its field stays focused, and the
// capture below sees exactly what the hotkey path sees. // capture below sees exactly what the hotkey path sees.
FLazyWnd: HWND; FLazyWnd: HWND;
// Dernier theme annonce par la page. La barrette est peinte a la main, donc
// elle a besoin de savoir - un bouton Win32 standard reste gris clair meme
// sous SetPreferredAppMode(ForceDark), qui ne themise que ce que le systeme
// dessine lui-meme (menus, ascenseurs, infobulles).
FDarkTheme: Boolean;
procedure LazyWindowHandler(var AMsg: TMessage); procedure LazyWindowHandler(var AMsg: TMessage);
/// Peint un bouton de la barrette (BS_OWNERDRAW). Le systeme garde le suivi
/// du clic et l'etat enfonce ; nous ne reprenons que les couleurs.
procedure DrawLazyButton(ADraw: PDrawItemStruct);
// Capture the foreground window and hand it to the host. Shared by the // Capture the foreground window and hand it to the host. Shared by the
// autofill hotkeys and the lazy strip - same target, same title matching, // autofill hotkeys and the lazy strip - same target, same title matching,
// same picker. Only the gesture differs. // same picker. Only the gesture differs.
@@ -377,9 +385,25 @@ const
const const
LAZY_ID_FULL = 101; LAZY_ID_FULL = 101;
LAZY_ID_PWD = 102; LAZY_ID_PWD = 102;
LAZY_W = 148; LAZY_ID_TOTP = 103;
LAZY_W = 206;
LAZY_H = 40; LAZY_H = 40;
// Palette de la barrette, en COLORREF ($00BBGGRR - les octets sont INVERSES
// par rapport a l'hexadecimal CSS). Reprise des variables de css/style.css pour
// que la barrette et la fenetre principale ne soient pas deux gris differents.
const
LAZY_DARK_BG = $1C1515; // --bg #15151c
LAZY_DARK_FACE = $322929; // --bg-elev-2 #292932
LAZY_DARK_DOWN = $3F3434; // --bg-elev-3 #34343f
LAZY_DARK_EDGE = $413535; // --border #353541
LAZY_DARK_INK = $F5F1F1; // --text #f1f1f5
LAZY_LITE_BG = $F5F7F7; // --bg #f7f7f5
LAZY_LITE_FACE = $FFFFFF; // --bg-elev #ffffff
LAZY_LITE_DOWN = $DDE3E3; // --bg-elev-3 #e3e3dd
LAZY_LITE_EDGE = $D7DCDC; // --border #dcdcd7
LAZY_LITE_INK = $1F1A1A; // --text #1a1a1f
// Dynamic WTS function pointers — wtsapi32.dll is not guaranteed on all // Dynamic WTS function pointers — wtsapi32.dll is not guaranteed on all
// Windows SKUs (e.g. minimal Server Core without Session Services), so // Windows SKUs (e.g. minimal Server Core without Session Services), so
// we load it at runtime and tolerate absence gracefully. // we load it at runtime and tolerate absence gracefully.
@@ -1011,6 +1035,41 @@ begin
FOnAutofillRequest(AKind, LTarget, LTitle); FOnAutofillRequest(AKind, LTarget, LTitle);
end; end;
procedure TPMBridge.DrawLazyButton(ADraw: PDrawItemStruct);
var
LRect: TRect;
LBrush: HBRUSH;
LFace, LEdge, LInk: COLORREF;
LText: array[0..31] of Char;
begin
LRect := ADraw.rcItem;
if FDarkTheme then
begin
LFace := LAZY_DARK_FACE; LEdge := LAZY_DARK_EDGE; LInk := LAZY_DARK_INK;
if (ADraw.itemState and ODS_SELECTED) <> 0 then LFace := LAZY_DARK_DOWN;
end
else
begin
LFace := LAZY_LITE_FACE; LEdge := LAZY_LITE_EDGE; LInk := LAZY_LITE_INK;
if (ADraw.itemState and ODS_SELECTED) <> 0 then LFace := LAZY_LITE_DOWN;
end;
LBrush := CreateSolidBrush(LFace);
FillRect(ADraw.hDC, LRect, LBrush);
DeleteObject(LBrush);
LBrush := CreateSolidBrush(LEdge);
FrameRect(ADraw.hDC, LRect, LBrush);
DeleteObject(LBrush);
// 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.
GetWindowText(ADraw.hwndItem, LText, Length(LText));
SetBkMode(ADraw.hDC, TRANSPARENT);
SetTextColor(ADraw.hDC, LInk);
DrawText(ADraw.hDC, LText, -1, LRect,
DT_CENTER or DT_VCENTER or DT_SINGLELINE);
end;
function TPMBridge.LazyBarVisible: Boolean; function TPMBridge.LazyBarVisible: Boolean;
begin begin
Result := FLazyWnd <> 0; Result := FLazyWnd <> 0;
@@ -1019,6 +1078,7 @@ end;
procedure TPMBridge.LazyWindowHandler(var AMsg: TMessage); procedure TPMBridge.LazyWindowHandler(var AMsg: TMessage);
var var
LRect: TRect; LRect: TRect;
LBrush: HBRUSH;
begin begin
case AMsg.Msg of case AMsg.Msg of
// THE STRIP IS ITS OWN TITLE BAR. It has none to grab, and it will end up // THE STRIP IS ITS OWN TITLE BAR. It has none to grab, and it will end up
@@ -1034,7 +1094,20 @@ begin
WM_ERASEBKGND: WM_ERASEBKGND:
begin begin
GetClientRect(FLazyWnd, LRect); GetClientRect(FLazyWnd, LRect);
FillRect(HDC(AMsg.WParam), LRect, HBRUSH(COLOR_BTNFACE + 1)); LBrush := CreateSolidBrush(IfThen(FDarkTheme, LAZY_DARK_BG, LAZY_LITE_BG));
FillRect(HDC(AMsg.WParam), LRect, LBrush);
DeleteObject(LBrush);
// Le cadre est peint ici plutot que par WS_BORDER : celui-ci se sert
// dans les couleurs systeme, qui ignorent notre theme.
LBrush := CreateSolidBrush(IfThen(FDarkTheme, LAZY_DARK_EDGE, LAZY_LITE_EDGE));
FrameRect(HDC(AMsg.WParam), LRect, LBrush);
DeleteObject(LBrush);
AMsg.Result := 1;
Exit;
end;
WM_DRAWITEM:
begin
DrawLazyButton(PDrawItemStruct(AMsg.LParam));
AMsg.Result := 1; AMsg.Result := 1;
Exit; Exit;
end; end;
@@ -1044,6 +1117,7 @@ begin
case LoWord(AMsg.WParam) of case LoWord(AMsg.WParam) of
LAZY_ID_FULL: FireAutofillRequest(akFull); LAZY_ID_FULL: FireAutofillRequest(akFull);
LAZY_ID_PWD: FireAutofillRequest(akPasswordOnly); LAZY_ID_PWD: FireAutofillRequest(akPasswordOnly);
LAZY_ID_TOTP: FireAutofillRequest(akTotp);
end; end;
AMsg.Result := 0; AMsg.Result := 0;
Exit; Exit;
@@ -1054,12 +1128,16 @@ end;
procedure TPMBridge.SetLazyBar(AOn: Boolean); procedure TPMBridge.SetLazyBar(AOn: Boolean);
procedure MakeButton(AId: Integer; const ACaption: string; AX: Integer); // 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
// sur fond sombre, comme sur la capture qui a motive ce changement.
procedure MakeButton(AId: Integer; const ACaption: string;
AX, AWidth: Integer);
var var
LBtn: HWND; LBtn: HWND;
begin begin
LBtn := CreateWindow('BUTTON', PChar(ACaption), LBtn := CreateWindow('BUTTON', PChar(ACaption),
WS_CHILD or WS_VISIBLE or BS_PUSHBUTTON, AX, 5, 66, 28, WS_CHILD or WS_VISIBLE or BS_OWNERDRAW, AX, 5, AWidth, 28,
FLazyWnd, HMENU(AId), HInstance, nil); FLazyWnd, HMENU(AId), HInstance, nil);
// Without this the button wears the 1995 system font. // Without this the button wears the 1995 system font.
if LBtn <> 0 then if LBtn <> 0 then
@@ -1083,11 +1161,10 @@ begin
SetWindowLong(FLazyWnd, GWL_EXSTYLE, SetWindowLong(FLazyWnd, GWL_EXSTYLE,
GetWindowLong(FLazyWnd, GWL_EXSTYLE) or GetWindowLong(FLazyWnd, GWL_EXSTYLE) or
WS_EX_NOACTIVATE or WS_EX_TOPMOST or WS_EX_TOOLWINDOW); WS_EX_NOACTIVATE or WS_EX_TOPMOST or WS_EX_TOOLWINDOW);
SetWindowLong(FLazyWnd, GWL_STYLE,
GetWindowLong(FLazyWnd, GWL_STYLE) or WS_BORDER);
MakeButton(LAZY_ID_FULL, 'Full', 5); MakeButton(LAZY_ID_FULL, 'Full', 5, 56);
MakeButton(LAZY_ID_PWD, 'Password', 77); MakeButton(LAZY_ID_PWD, 'Password', 67, 76);
MakeButton(LAZY_ID_TOTP, '2FA', 149, 52);
// 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.
@@ -1330,6 +1407,13 @@ var
DarkFlag: BOOL; DarkFlag: BOOL;
FormHwnd: HWND; FormHwnd: HWND;
begin begin
FDarkTheme := ADark;
// La barrette ne suit aucun theme systeme : c'est nous qui la peignons, donc
// c'est nous qui la redessinons quand la page bascule.
if FLazyWnd <> 0 then
RedrawWindow(FLazyWnd, nil, 0,
RDW_INVALIDATE or RDW_ERASE or RDW_ALLCHILDREN or RDW_UPDATENOW);
if FMainForm = nil then Exit; if FMainForm = nil then Exit;
FormHwnd := MainFormHWND(FMainForm); FormHwnd := MainFormHWND(FMainForm);
if FormHwnd = 0 then Exit; if FormHwnd = 0 then Exit;