From 52f5024de0ef723cc82e15d948dd5e1b5a5114c7 Mon Sep 17 00:00:00 2001 From: r-zakarya <82443831+r-zakarya@users.noreply.github.com> Date: Wed, 26 Aug 2026 15:49:49 +0100 Subject: [PATCH] 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 --- CLAUDE.md | 14 +++- delphi-backend/Source/PM.Bridge.pas | 102 +++++++++++++++++++++++++--- 2 files changed, 104 insertions(+), 12 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 5a55b1b..29f534c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -162,9 +162,9 @@ via `settings_json`). ## Lazy mode (remplir à la souris) -Menu du tray → **Lazy mode (fill with the mouse)** : une barrette de deux -boutons *Full* / *Password* qui flotte au-dessus de tout et fait exactement ce -que font `Ctrl+Shift+L` / `Ctrl+Shift+P`. +Menu du tray → **Lazy mode (fill with the mouse)** : une barrette de trois +boutons *Full* / *Password* / *2FA* qui flotte au-dessus de tout et fait +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 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` d'`AllocateHWnd` n'a **pas** de brosse de fond. - 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 diff --git a/delphi-backend/Source/PM.Bridge.pas b/delphi-backend/Source/PM.Bridge.pas index 19d639f..d4f3753 100644 --- a/delphi-backend/Source/PM.Bridge.pas +++ b/delphi-backend/Source/PM.Bridge.pas @@ -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;