1010
1111#include " resource.h"
1212
13+ #include < dwmapi.h>
14+
1315TRACELOGGING_DEFINE_PROVIDER (
1416 g_hProvider,
1517 " Microsoft.PowerToys" ,
@@ -21,6 +23,7 @@ TRACELOGGING_DEFINE_PROVIDER(
2123// Globals
2224// ---------------------------------------------------------------------------
2325static HINSTANCE g_hInstance = nullptr ;
26+ static ULONG_PTR g_gdiplusToken = 0 ; // GDI+ token for overlay border rendering
2427static HHOOK g_hhkKeyboard = nullptr ;
2528static HHOOK g_hhkMouse = nullptr ;
2629static HWND g_hMsgWnd = nullptr ;
@@ -47,6 +50,25 @@ static HWND g_hOverlay = nullptr; // semi-transparent overlay during drag
4750static int g_overlayInfoX = 0 , g_overlayInfoY = 0 ;
4851static int g_overlayInfoW = 0 , g_overlayInfoH = 0 ;
4952
53+ // Visible-frame overlay border metrics. Computed once per drag/resize (cold path)
54+ // and reused while rendering - never recomputed in the mouse-move hot path.
55+ // Margins are the difference between GetWindowRect and the DWM extended frame
56+ // bounds (the invisible resize border), so the border hugs the visible window.
57+ static int g_overlayMarginL = 0 , g_overlayMarginT = 0 , g_overlayMarginR = 0 , g_overlayMarginB = 0 ;
58+ static int g_overlayCornerRadius = 0 ; // physical px; 0 = square corners
59+ static int g_overlayBorderThickness = 4 ; // physical px
60+ static bool g_overlayAotActive = false ; // target window is pinned by Always On Top
61+
62+ // Fluent "warning" gold - the literal equivalent of WinUI SystemFillColorCaution
63+ // (used as a ThemeResource for warnings across the Settings UI). A Win32 layered
64+ // window can't resolve a ThemeResource, so the literal is required here.
65+ static constexpr COLORREF OVERLAY_BORDER_COLOR = RGB (255 , 185 , 0 ); // #FFB900
66+
67+ // Base border thickness in DIPs (scaled by the target window DPI). Doubled when the
68+ // target is also pinned by Always On Top so AoT's own accent border layers on top.
69+ static constexpr int OVERLAY_BORDER_DIP = 4 ;
70+ static constexpr int OVERLAY_BORDER_DIP_AOT = 8 ;
71+
5072static bool g_shouldAbsorbAlt =
5173 true ; // true if we want to absorb Alt on the next keydown (set when Alt is pressed without dragging, cleared on next non-Alt key or Alt keyup)
5274static bool g_altAbsorbed = false ; // true if we absorbed an Alt keydown
@@ -128,6 +150,12 @@ static const wchar_t* const CLASS_NAME = L"GrabAndMove_MsgWnd";
128150static const wchar_t * const OVERLAY_CLASS_NAME = L" GrabAndMove_Overlay" ;
129151static const wchar_t * const APP_TITLE = L" GrabAndMove" ;
130152
153+ // Must match Always On Top's border tool-window class
154+ // (NonLocalizable::ToolWindowClassName in
155+ // src\modules\alwaysontop\AlwaysOnTop\WindowBorder.cpp). Used to detect whether the
156+ // target window is currently pinned by Always On Top.
157+ static const wchar_t * const ALWAYS_ON_TOP_BORDER_CLASS = L" AlwaysOnTop_Border" ;
158+
131159// Must match CommonSharedConstants::GRABANDMOVE_REFRESH_SETTINGS_EVENT in shared_constants.h
132160static const wchar_t * const GRABANDMOVE_REFRESH_SETTINGS_EVENT =
133161 L" Local\\ PowerToysGrabAndMove-RefreshSettingsEvent-a7b3c1d2-4e5f-6a7b-8c9d-0e1f2a3b4c5d" ;
@@ -343,9 +371,168 @@ static void SettingsWatcherThread(DWORD mainThreadId)
343371static int g_overlayRenderedW = 0 ;
344372static int g_overlayRenderedH = 0 ;
345373
374+ // Maps the DWM window corner preference to a base radius in DIPs, matching
375+ // Always On Top (WindowCornerUtils::CornersRadius).
376+ static int CornerRadiusForWindow (HWND hwnd)
377+ {
378+ int pref = 0 ; // DWMWCP_DEFAULT
379+ if (DwmGetWindowAttribute (hwnd, DWMWA_WINDOW_CORNER_PREFERENCE , &pref, sizeof (pref)) != S_OK )
380+ {
381+ return 0 ; // pre-Win11 / unsupported -> square corners
382+ }
383+
384+ switch (pref)
385+ {
386+ case DWMWCP_ROUND :
387+ return 8 ;
388+ case DWMWCP_ROUNDSMALL :
389+ return 4 ;
390+ case DWMWCP_DEFAULT :
391+ return 8 ;
392+ default :
393+ return 0 ; // DWMWCP_DONOTROUND
394+ }
395+ }
396+
397+ // Returns true if the target window currently has an Always On Top border around it.
398+ // AoT creates a topmost tool window of class ALWAYS_ON_TOP_BORDER_CLASS sized to the
399+ // pinned window; a substantial overlap is treated as a match. Enumerates top-level
400+ // windows, so it is only called at the start of a drag/resize, never per mouse move.
401+ static bool IsWindowPinnedByAlwaysOnTop (HWND target)
402+ {
403+ RECT targetRect{};
404+ if (!GetWindowRect (target, &targetRect))
405+ {
406+ return false ;
407+ }
408+
409+ struct EnumCtx
410+ {
411+ RECT target;
412+ bool found;
413+ } ctx{ targetRect, false };
414+
415+ EnumWindows (
416+ [](HWND hwnd, LPARAM lparam) -> BOOL {
417+ auto * c = reinterpret_cast <EnumCtx*>(lparam);
418+ wchar_t cls[64 ]{};
419+ if (GetClassNameW (hwnd, cls, ARRAYSIZE (cls)) == 0 || wcscmp (cls, ALWAYS_ON_TOP_BORDER_CLASS ) != 0 )
420+ {
421+ return TRUE ;
422+ }
423+
424+ RECT border{};
425+ RECT inter{};
426+ if (GetWindowRect (hwnd, &border) && IntersectRect (&inter, &border, &c->target ))
427+ {
428+ const long long interArea = (static_cast <long long >(inter.right ) - inter.left ) * (static_cast <long long >(inter.bottom ) - inter.top );
429+ const long long targetArea = (static_cast <long long >(c->target .right ) - c->target .left ) * (static_cast <long long >(c->target .bottom ) - c->target .top );
430+ if (targetArea > 0 && interArea * 100 >= targetArea * 80 )
431+ {
432+ c->found = true ;
433+ return FALSE ; // stop enumeration
434+ }
435+ }
436+ return TRUE ;
437+ },
438+ reinterpret_cast <LPARAM >(&ctx));
439+
440+ return ctx.found ;
441+ }
442+
443+ // Computes the overlay border metrics (margins to the visible frame, corner radius,
444+ // thickness, AoT state) for the target window. Cold path only: called at the start of
445+ // a drag/resize and after un-maximize, never from the mouse-move hot path.
446+ static void PrepareOverlayMetrics (HWND target)
447+ {
448+ g_overlayMarginL = g_overlayMarginT = g_overlayMarginR = g_overlayMarginB = 0 ;
449+ g_overlayCornerRadius = 0 ;
450+ g_overlayAotActive = false ;
451+ g_overlayBorderThickness = OVERLAY_BORDER_DIP ;
452+
453+ if (!target)
454+ {
455+ return ;
456+ }
457+
458+ const UINT dpi = GetDpiForWindow (target);
459+ const float scale = (dpi != 0 ) ? dpi / 96 .0f : 1 .0f ;
460+
461+ RECT windowRect{};
462+ RECT frameRect{};
463+ if (GetWindowRect (target, &windowRect) &&
464+ SUCCEEDED (DwmGetWindowAttribute (target, DWMWA_EXTENDED_FRAME_BOUNDS , &frameRect, sizeof (frameRect))))
465+ {
466+ g_overlayMarginL = max (0 , static_cast <int >(frameRect.left - windowRect.left ));
467+ g_overlayMarginT = max (0 , static_cast <int >(frameRect.top - windowRect.top ));
468+ g_overlayMarginR = max (0 , static_cast <int >(windowRect.right - frameRect.right ));
469+ g_overlayMarginB = max (0 , static_cast <int >(windowRect.bottom - frameRect.bottom ));
470+ }
471+
472+ g_overlayCornerRadius = static_cast <int >(CornerRadiusForWindow (target) * scale);
473+ g_overlayAotActive = IsWindowPinnedByAlwaysOnTop (target);
474+ g_overlayBorderThickness = static_cast <int >((g_overlayAotActive ? OVERLAY_BORDER_DIP_AOT : OVERLAY_BORDER_DIP ) * scale);
475+ }
476+
477+ // Draws an antialiased (optionally rounded) border stroke fully inside `rect` using
478+ // GDI+. The stroke hugs the inner edge of `rect` (the visible window frame).
479+ static void DrawOverlayBorder (Gdiplus::Graphics& graphics, const RECT & rect, int thickness, int radius)
480+ {
481+ const int w = rect.right - rect.left ;
482+ const int h = rect.bottom - rect.top ;
483+ if (w <= 0 || h <= 0 || thickness <= 0 )
484+ {
485+ return ;
486+ }
487+
488+ // Keep the whole stroke inside the visible frame on every side.
489+ thickness = min (thickness, min (w, h) / 2 );
490+ if (thickness <= 0 )
491+ {
492+ return ;
493+ }
494+
495+ const float half = thickness / 2 .0f ;
496+ const Gdiplus::RectF path (
497+ rect.left + half,
498+ rect.top + half,
499+ static_cast <Gdiplus::REAL >(w) - thickness,
500+ static_cast <Gdiplus::REAL >(h) - thickness);
501+
502+ graphics.SetSmoothingMode (Gdiplus::SmoothingModeAntiAlias);
503+ Gdiplus::Pen pen (
504+ Gdiplus::Color (255 , GetRValue (OVERLAY_BORDER_COLOR ), GetGValue (OVERLAY_BORDER_COLOR ), GetBValue (OVERLAY_BORDER_COLOR )),
505+ static_cast <Gdiplus::REAL >(thickness));
506+
507+ if (radius <= 0 )
508+ {
509+ graphics.DrawRectangle (&pen, path);
510+ return ;
511+ }
512+
513+ // The stroke is centred, so the path corner radius is the window radius minus
514+ // half the thickness; that keeps the outer edge aligned with the window corner.
515+ const float pathRadius = max (0 .0f , radius - half);
516+ const float diameter = min (pathRadius * 2 .0f , min (path.Width , path.Height ));
517+ if (diameter <= 0 .0f )
518+ {
519+ graphics.DrawRectangle (&pen, path);
520+ return ;
521+ }
522+
523+ Gdiplus::GraphicsPath border;
524+ border.AddArc (path.X , path.Y , diameter, diameter, 180 .0f , 90 .0f );
525+ border.AddArc (path.GetRight () - diameter, path.Y , diameter, diameter, 270 .0f , 90 .0f );
526+ border.AddArc (path.GetRight () - diameter, path.GetBottom () - diameter, diameter, diameter, 0 .0f , 90 .0f );
527+ border.AddArc (path.X , path.GetBottom () - diameter, diameter, diameter, 90 .0f , 90 .0f );
528+ border.CloseFigure ();
529+ graphics.DrawPath (&pen, &border);
530+ }
531+
346532// Renders the overlay surface using per-pixel alpha via UpdateLayeredWindow.
347- // The white background is painted at ~40% opacity; the geometry label box is
348- // painted fully opaque so it remains legible regardless of what is beneath.
533+ // The surface is fully transparent except for a tight warning-gold border around the
534+ // visible window frame; the optional geometry label box is painted fully opaque so it
535+ // remains legible regardless of what is beneath.
349536static void RenderOverlayContent (HWND hwnd, int cw, int ch)
350537{
351538 if (!hwnd || cw <= 0 || ch <= 0 )
@@ -372,8 +559,24 @@ static void RenderOverlayContent(HWND hwnd, int cw, int ch)
372559 HDC memDC = CreateCompatibleDC (screenDC);
373560 HBITMAP hOldBmp = static_cast <HBITMAP >(SelectObject (memDC, hDib));
374561
375- // Premultiplied white at ~40% opacity: A=0x66, R=G=B=0x66 → 0x66666666
376- memset (pBits, 0x66 , static_cast <size_t >(cw) * ch * sizeof (DWORD ));
562+ // Start fully transparent; we paint only a tight border (+ optional label).
563+ memset (pBits, 0 , static_cast <size_t >(cw) * ch * sizeof (DWORD ));
564+
565+ // Warning-gold border hugging the visible window frame. The overlay window spans
566+ // GetWindowRect, so inset by the invisible-border margins so the stroke lands on
567+ // the visible edge (mirrors Always On Top's tight border).
568+ {
569+ const RECT visible = {
570+ g_overlayMarginL,
571+ g_overlayMarginT,
572+ cw - g_overlayMarginR,
573+ ch - g_overlayMarginB
574+ };
575+ Gdiplus::Bitmap bitmap (cw, ch, cw * 4 , PixelFormat32bppPARGB, reinterpret_cast <BYTE *>(pBits));
576+ Gdiplus::Graphics graphics (&bitmap);
577+ DrawOverlayBorder (graphics, visible, g_overlayBorderThickness, g_overlayCornerRadius);
578+ graphics.Flush ();
579+ }
377580
378581 if (g_showGeometry)
379582 {
@@ -1045,6 +1248,7 @@ static LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam)
10451248 g_dragTarget = hwnd;
10461249 g_dragStart = pt;
10471250 GetWindowRect (hwnd, &g_dragWndRect);
1251+ PrepareOverlayMetrics (hwnd);
10481252
10491253 // Show the semi-transparent overlay on top of the target (persistent window – fix #9)
10501254 ShowOverlay (g_dragWndRect, g_curSizeAll);
@@ -1112,6 +1316,7 @@ static LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam)
11121316 g_resizeTarget = hwnd;
11131317 g_resizeLast = pt;
11141318 GetWindowRect (hwnd, &g_resizeWndRect);
1319+ PrepareOverlayMetrics (hwnd);
11151320
11161321 g_currentHandle = GetClosestHandle (pt, g_resizeWndRect);
11171322 ShowOverlay (g_resizeWndRect, CursorForHandle (g_currentHandle));
@@ -1183,6 +1388,9 @@ static void HandleDragMove(POINT pt)
11831388
11841389 g_dragStart = pt;
11851390 g_dragWndRect = {newX, newY, newX + restoredW, newY + restoredH};
1391+
1392+ // Corner radius / invisible-border margins differ once restored.
1393+ PrepareOverlayMetrics (g_dragTarget);
11861394 }
11871395 }
11881396
@@ -1230,6 +1438,9 @@ static void HandleDragResize(POINT pt)
12301438 SWP_NOZORDER | SWP_NOACTIVATE | SWP_ASYNCWINDOWPOS );
12311439 g_resizeWndRect = {newLeft, newTop, newLeft + newW, newTop + newH};
12321440
1441+ // Corner radius / invisible-border margins differ once restored.
1442+ PrepareOverlayMetrics (g_resizeTarget);
1443+
12331444 g_resizeLast = pt;
12341445 g_currentHandle = GetClosestHandle (pt, g_resizeWndRect);
12351446 }
@@ -1375,6 +1586,10 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR lpCmdLine, int)
13751586 INITCOMMONCONTROLSEX commonControls = { sizeof (commonControls), ICC_STANDARD_CLASSES };
13761587 InitCommonControlsEx (&commonControls);
13771588
1589+ // Initialise GDI+ for antialiased overlay border rendering
1590+ Gdiplus::GdiplusStartupInput gdiplusStartupInput;
1591+ Gdiplus::GdiplusStartup (&g_gdiplusToken, &gdiplusStartupInput, nullptr );
1592+
13781593 // Register a message-only window class
13791594 WNDCLASSEXW wc = {};
13801595 wc.cbSize = sizeof (wc);
@@ -1387,13 +1602,13 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR lpCmdLine, int)
13871602 return 1 ;
13881603 }
13891604
1390- // Register the overlay window class (white background , ARROW cursor)
1605+ // Register the overlay window class (layered per-pixel-alpha surface , ARROW cursor)
13911606 WNDCLASSEXW overlayWindowClass = {};
13921607 overlayWindowClass.cbSize = sizeof (overlayWindowClass);
13931608 overlayWindowClass.lpfnWndProc = DefWindowProcW;
13941609 overlayWindowClass.hInstance = hInstance;
13951610 overlayWindowClass.hCursor = LoadCursorW (nullptr , IDC_ARROW );
1396- overlayWindowClass.hbrBackground = static_cast < HBRUSH >( GetStockObject ( WHITE_BRUSH ));
1611+ overlayWindowClass.hbrBackground = nullptr ; // per-pixel alpha via UpdateLayeredWindow
13971612 overlayWindowClass.lpszClassName = OVERLAY_CLASS_NAME ;
13981613 if (!RegisterClassExW (&overlayWindowClass))
13991614 {
@@ -1482,6 +1697,11 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR lpCmdLine, int)
14821697 g_hOverlay = nullptr ;
14831698 }
14841699 RemoveTrayIcon ();
1700+ if (g_gdiplusToken)
1701+ {
1702+ Gdiplus::GdiplusShutdown (g_gdiplusToken);
1703+ g_gdiplusToken = 0 ;
1704+ }
14851705 TraceLoggingUnregister (g_hProvider);
14861706
14871707 return static_cast <int >(msg.wParam );
0 commit comments