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
+93 -9
View File
@@ -29,7 +29,7 @@ unit PM.Bridge;
interface
uses
System.SysUtils, System.Classes, System.Math,
System.SysUtils, System.Classes, System.Math, System.Types,
FMX.Types, FMX.Forms,
Winapi.Windows, Winapi.ShellAPI, Winapi.Messages,
PM.SingleInstance;
@@ -152,7 +152,15 @@ type
// foreground: the browser stays active, its field stays focused, and the
// capture below sees exactly what the hotkey path sees.
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);
/// 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
// autofill hotkeys and the lazy strip - same target, same title matching,
// same picker. Only the gesture differs.
@@ -377,9 +385,25 @@ const
const
LAZY_ID_FULL = 101;
LAZY_ID_PWD = 102;
LAZY_W = 148;
LAZY_ID_TOTP = 103;
LAZY_W = 206;
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
// Windows SKUs (e.g. minimal Server Core without Session Services), so
// we load it at runtime and tolerate absence gracefully.
@@ -1011,6 +1035,41 @@ begin
FOnAutofillRequest(AKind, LTarget, LTitle);
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;
begin
Result := FLazyWnd <> 0;
@@ -1019,6 +1078,7 @@ end;
procedure TPMBridge.LazyWindowHandler(var AMsg: TMessage);
var
LRect: TRect;
LBrush: HBRUSH;
begin
case AMsg.Msg of
// THE STRIP IS ITS OWN TITLE BAR. It has none to grab, and it will end up
@@ -1034,7 +1094,20 @@ begin
WM_ERASEBKGND:
begin
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;
Exit;
end;
@@ -1044,6 +1117,7 @@ begin
case LoWord(AMsg.WParam) of
LAZY_ID_FULL: FireAutofillRequest(akFull);
LAZY_ID_PWD: FireAutofillRequest(akPasswordOnly);
LAZY_ID_TOTP: FireAutofillRequest(akTotp);
end;
AMsg.Result := 0;
Exit;
@@ -1054,12 +1128,16 @@ end;
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
LBtn: HWND;
begin
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);
// Without this the button wears the 1995 system font.
if LBtn <> 0 then
@@ -1083,11 +1161,10 @@ begin
SetWindowLong(FLazyWnd, GWL_EXSTYLE,
GetWindowLong(FLazyWnd, GWL_EXSTYLE) or
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_PWD, 'Password', 77);
MakeButton(LAZY_ID_FULL, 'Full', 5, 56);
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
// taskbar. From there the user drags it wherever the login form is.
@@ -1330,6 +1407,13 @@ var
DarkFlag: BOOL;
FormHwnd: HWND;
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;
FormHwnd := MainFormHWND(FMainForm);
if FormHwnd = 0 then Exit;