Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/actions/spell-check/allow/code.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ LIGHTTURQUOISE
NCol
OLIVEGREEN
PALEBLUE
PARGB
PArgb
Comment thread
crutkas marked this conversation as resolved.
Outdated
Pbgra
SRGBTo
Expand Down
2 changes: 1 addition & 1 deletion src/modules/GrabAndMove/GrabAndMove/GrabAndMove.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@
</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<AdditionalDependencies>WindowsApp.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>WindowsApp.lib;Gdiplus.lib;Dwmapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
Expand Down
212 changes: 206 additions & 6 deletions src/modules/GrabAndMove/GrabAndMove/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

#include "resource.h"

#include <dwmapi.h>

TRACELOGGING_DEFINE_PROVIDER(
g_hProvider,
"Microsoft.PowerToys",
Expand All @@ -21,6 +23,7 @@ TRACELOGGING_DEFINE_PROVIDER(
// Globals
// ---------------------------------------------------------------------------
static HINSTANCE g_hInstance = nullptr;
static ULONG_PTR g_gdiplusToken = 0; // GDI+ token for overlay border rendering
static HHOOK g_hhkKeyboard = nullptr;
static HHOOK g_hhkMouse = nullptr;
static HWND g_hMsgWnd = nullptr;
Expand All @@ -47,6 +50,29 @@ static HWND g_hOverlay = nullptr; // semi-transparent overlay during drag
static int g_overlayInfoX = 0, g_overlayInfoY = 0;
static int g_overlayInfoW = 0, g_overlayInfoH = 0;

// Visible-frame overlay metrics. Computed once per drag/resize (cold path) and
Comment thread
crutkas marked this conversation as resolved.
Outdated
// reused while rendering - never recomputed in the mouse-move hot path.
// Margins are the difference between GetWindowRect and the DWM extended frame
// bounds (the invisible resize border), so the fill and border hug the visible
// window. The border is drawn just inside the visible edge; Always On Top draws
// its own border just outside that edge, so the two stack into a clean double
// layer without Grab and Move having to widen its stroke.
static int g_overlayMarginL = 0, g_overlayMarginT = 0, g_overlayMarginR = 0, g_overlayMarginB = 0;
static int g_overlayCornerRadius = 0; // physical px; 0 = square corners
static int g_overlayBorderThickness = 4; // physical px

// Fluent "warning" gold - the literal equivalent of WinUI SystemFillColorCaution
Comment thread
crutkas marked this conversation as resolved.
Outdated
// (used as a ThemeResource for warnings across the Settings UI). A Win32 layered
// window can't resolve a ThemeResource, so the literal is required here.
static constexpr COLORREF OVERLAY_BORDER_COLOR = RGB(255, 185, 0); // #FFB900

// Border thickness in DIPs (scaled by the target window DPI).
static constexpr int OVERLAY_BORDER_DIP = 4;

// Translucent white wash painted over the visible window during a drag/resize,
// matching the prior overlay. ~40% opacity (premultiplied white = 0x66666666).
static constexpr BYTE OVERLAY_FILL_ALPHA = 0x66;

static bool g_shouldAbsorbAlt =
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)
static bool g_altAbsorbed = false; // true if we absorbed an Alt keydown
Expand Down Expand Up @@ -343,9 +369,122 @@ static void SettingsWatcherThread(DWORD mainThreadId)
static int g_overlayRenderedW = 0;
static int g_overlayRenderedH = 0;

// Maps the DWM window corner preference to a base radius in DIPs, matching
// Always On Top (WindowCornerUtils::CornersRadius).
static int CornerRadiusForWindow(HWND hwnd)
{
int pref = 0; // DWMWCP_DEFAULT
if (DwmGetWindowAttribute(hwnd, DWMWA_WINDOW_CORNER_PREFERENCE, &pref, sizeof(pref)) != S_OK)
{
return 0; // pre-Win11 / unsupported -> square corners
}

switch (pref)
{
case DWMWCP_ROUND:
return 8;
case DWMWCP_ROUNDSMALL:
return 4;
case DWMWCP_DEFAULT:
return 8;
default:
return 0; // DWMWCP_DONOTROUND
}
}

// Computes the overlay metrics (margins to the visible frame, corner radius, border
// thickness) for the target window. Cold path only: called at the start of a
// drag/resize and after un-maximize, never from the mouse-move hot path.
static void PrepareOverlayMetrics(HWND target)
{
g_overlayMarginL = g_overlayMarginT = g_overlayMarginR = g_overlayMarginB = 0;
g_overlayCornerRadius = 0;
g_overlayBorderThickness = OVERLAY_BORDER_DIP;

if (!target)
{
return;
}

const UINT dpi = GetDpiForWindow(target);
const float scale = (dpi != 0) ? dpi / 96.0f : 1.0f;

RECT windowRect{};
RECT frameRect{};
if (GetWindowRect(target, &windowRect) &&
SUCCEEDED(DwmGetWindowAttribute(target, DWMWA_EXTENDED_FRAME_BOUNDS, &frameRect, sizeof(frameRect))))
{
g_overlayMarginL = max(0, static_cast<int>(frameRect.left - windowRect.left));
g_overlayMarginT = max(0, static_cast<int>(frameRect.top - windowRect.top));
g_overlayMarginR = max(0, static_cast<int>(windowRect.right - frameRect.right));
g_overlayMarginB = max(0, static_cast<int>(windowRect.bottom - frameRect.bottom));
}

g_overlayCornerRadius = static_cast<int>(CornerRadiusForWindow(target) * scale);
g_overlayBorderThickness = static_cast<int>(OVERLAY_BORDER_DIP * scale);
}

// Draws an antialiased (optionally rounded) border stroke fully inside `rect` using
// GDI+. The stroke hugs the inner edge of `rect` (the visible window frame).
static void DrawOverlayBorder(Gdiplus::Graphics& graphics, const RECT& rect, int thickness, int radius)
{
const int w = rect.right - rect.left;
const int h = rect.bottom - rect.top;
if (w <= 0 || h <= 0 || thickness <= 0)
{
return;
}

// Keep the whole stroke inside the visible frame on every side.
thickness = min(thickness, min(w, h) / 2);
if (thickness <= 0)
{
return;
}

const float half = thickness / 2.0f;
const Gdiplus::RectF path(
rect.left + half,
rect.top + half,
static_cast<Gdiplus::REAL>(w) - thickness,
static_cast<Gdiplus::REAL>(h) - thickness);

graphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
Gdiplus::Pen pen(
Gdiplus::Color(255, GetRValue(OVERLAY_BORDER_COLOR), GetGValue(OVERLAY_BORDER_COLOR), GetBValue(OVERLAY_BORDER_COLOR)),
static_cast<Gdiplus::REAL>(thickness));

if (radius <= 0)
{
graphics.DrawRectangle(&pen, path);
return;
}

// The stroke is centred, so the path corner radius is the window radius minus
// half the thickness; that keeps the outer edge aligned with the window corner.
const float pathRadius = max(0.0f, radius - half);
const float diameter = min(pathRadius * 2.0f, min(path.Width, path.Height));
if (diameter <= 0.0f)
{
graphics.DrawRectangle(&pen, path);
return;
}

Gdiplus::GraphicsPath border;
border.AddArc(path.X, path.Y, diameter, diameter, 180.0f, 90.0f);
border.AddArc(path.GetRight() - diameter, path.Y, diameter, diameter, 270.0f, 90.0f);
border.AddArc(path.GetRight() - diameter, path.GetBottom() - diameter, diameter, diameter, 0.0f, 90.0f);
border.AddArc(path.X, path.GetBottom() - diameter, diameter, diameter, 90.0f, 90.0f);
border.CloseFigure();
graphics.DrawPath(&pen, &border);
}

// Renders the overlay surface using per-pixel alpha via UpdateLayeredWindow.
// Renders the overlay surface using per-pixel alpha via UpdateLayeredWindow.
Comment thread
crutkas marked this conversation as resolved.
Outdated
// The white background is painted at ~40% opacity; the geometry label box is
// painted fully opaque so it remains legible regardless of what is beneath.
// A translucent white wash covers the visible window (matching the prior overlay)
// with a tight warning-gold border on top, both hugging the visible window frame;
// the optional geometry label box is painted fully opaque so it remains legible
// regardless of what is beneath.
static void RenderOverlayContent(HWND hwnd, int cw, int ch)
{
if (!hwnd || cw <= 0 || ch <= 0)
Expand All @@ -372,8 +511,52 @@ static void RenderOverlayContent(HWND hwnd, int cw, int ch)
HDC memDC = CreateCompatibleDC(screenDC);
HBITMAP hOldBmp = static_cast<HBITMAP>(SelectObject(memDC, hDib));

// Premultiplied white at ~40% opacity: A=0x66, R=G=B=0x66 → 0x66666666
memset(pBits, 0x66, static_cast<size_t>(cw) * ch * sizeof(DWORD));
// Start fully transparent.
memset(pBits, 0, static_cast<size_t>(cw) * ch * sizeof(DWORD));

// Translucent white wash over the visible window (prior-system fill) with a tight
Comment thread
crutkas marked this conversation as resolved.
Outdated
// warning-gold border on top. The overlay window spans GetWindowRect, so inset by
Comment thread
crutkas marked this conversation as resolved.
Outdated
// the invisible-border margins so both hug the visible edge; Always On Top draws
Comment thread
crutkas marked this conversation as resolved.
// its own border just outside that edge, giving a clean double layer.
{
const RECT visible = {
g_overlayMarginL,
g_overlayMarginT,
cw - g_overlayMarginR,
ch - g_overlayMarginB
};
const int vw = visible.right - visible.left;
const int vh = visible.bottom - visible.top;

Gdiplus::Bitmap bitmap(cw, ch, cw * 4, PixelFormat32bppPARGB, reinterpret_cast<BYTE*>(pBits));
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Gdiplus::Graphics graphics(&bitmap);
graphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);

if (vw > 0 && vh > 0)
{
Gdiplus::SolidBrush fillBrush(Gdiplus::Color(OVERLAY_FILL_ALPHA, 255, 255, 255));
if (g_overlayCornerRadius > 0)
{
// Round the wash to match the window corners (and the border).
const float d = min(static_cast<float>(g_overlayCornerRadius) * 2.0f,
static_cast<float>(min(vw, vh)));
Gdiplus::GraphicsPath fillPath;
fillPath.AddArc(static_cast<float>(visible.left), static_cast<float>(visible.top), d, d, 180.0f, 90.0f);
fillPath.AddArc(static_cast<float>(visible.right) - d, static_cast<float>(visible.top), d, d, 270.0f, 90.0f);
fillPath.AddArc(static_cast<float>(visible.right) - d, static_cast<float>(visible.bottom) - d, d, d, 0.0f, 90.0f);
fillPath.AddArc(static_cast<float>(visible.left), static_cast<float>(visible.bottom) - d, d, d, 90.0f, 90.0f);
fillPath.CloseFigure();
graphics.FillPath(&fillBrush, &fillPath);
}
else
{
graphics.FillRectangle(&fillBrush, visible.left, visible.top, vw, vh);
}
}

DrawOverlayBorder(graphics, visible, g_overlayBorderThickness, g_overlayCornerRadius);
graphics.Flush();
}

if (g_showGeometry)
{
Expand Down Expand Up @@ -1045,6 +1228,7 @@ static LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam)
g_dragTarget = hwnd;
g_dragStart = pt;
GetWindowRect(hwnd, &g_dragWndRect);
PrepareOverlayMetrics(hwnd);

// Show the semi-transparent overlay on top of the target (persistent window – fix #9)
ShowOverlay(g_dragWndRect, g_curSizeAll);
Expand Down Expand Up @@ -1112,6 +1296,7 @@ static LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam)
g_resizeTarget = hwnd;
g_resizeLast = pt;
GetWindowRect(hwnd, &g_resizeWndRect);
PrepareOverlayMetrics(hwnd);

g_currentHandle = GetClosestHandle(pt, g_resizeWndRect);
ShowOverlay(g_resizeWndRect, CursorForHandle(g_currentHandle));
Expand Down Expand Up @@ -1183,6 +1368,9 @@ static void HandleDragMove(POINT pt)

g_dragStart = pt;
g_dragWndRect = {newX, newY, newX + restoredW, newY + restoredH};

// Corner radius / invisible-border margins differ once restored.
PrepareOverlayMetrics(g_dragTarget);
}
}

Expand Down Expand Up @@ -1230,6 +1418,9 @@ static void HandleDragResize(POINT pt)
SWP_NOZORDER | SWP_NOACTIVATE | SWP_ASYNCWINDOWPOS);
g_resizeWndRect = {newLeft, newTop, newLeft + newW, newTop + newH};

// Corner radius / invisible-border margins differ once restored.
PrepareOverlayMetrics(g_resizeTarget);

g_resizeLast = pt;
g_currentHandle = GetClosestHandle(pt, g_resizeWndRect);
}
Expand Down Expand Up @@ -1375,6 +1566,10 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR lpCmdLine, int)
INITCOMMONCONTROLSEX commonControls = { sizeof(commonControls), ICC_STANDARD_CLASSES };
InitCommonControlsEx(&commonControls);

// Initialise GDI+ for antialiased overlay border rendering
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
Gdiplus::GdiplusStartup(&g_gdiplusToken, &gdiplusStartupInput, nullptr);

// Register a message-only window class
WNDCLASSEXW wc = {};
wc.cbSize = sizeof(wc);
Expand All @@ -1387,13 +1582,13 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR lpCmdLine, int)
return 1;
}

// Register the overlay window class (white background, ARROW cursor)
// Register the overlay window class (layered per-pixel-alpha surface, ARROW cursor)
WNDCLASSEXW overlayWindowClass = {};
overlayWindowClass.cbSize = sizeof(overlayWindowClass);
overlayWindowClass.lpfnWndProc = DefWindowProcW;
overlayWindowClass.hInstance = hInstance;
overlayWindowClass.hCursor = LoadCursorW(nullptr, IDC_ARROW);
overlayWindowClass.hbrBackground = static_cast<HBRUSH>(GetStockObject(WHITE_BRUSH));
overlayWindowClass.hbrBackground = nullptr; // per-pixel alpha via UpdateLayeredWindow
overlayWindowClass.lpszClassName = OVERLAY_CLASS_NAME;
if (!RegisterClassExW(&overlayWindowClass))
{
Expand Down Expand Up @@ -1482,6 +1677,11 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR lpCmdLine, int)
g_hOverlay = nullptr;
}
RemoveTrayIcon();
if (g_gdiplusToken)
{
Gdiplus::GdiplusShutdown(g_gdiplusToken);
g_gdiplusToken = 0;
}
TraceLoggingUnregister(g_hProvider);

return static_cast<int>(msg.wParam);
Expand Down
2 changes: 2 additions & 0 deletions src/modules/GrabAndMove/GrabAndMove/pch.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <objidl.h>
#include <gdiplus.h>
#include <shellapi.h>
#include <commctrl.h>
#include <TraceLoggingProvider.h>
Expand Down
2 changes: 1 addition & 1 deletion src/modules/alwaysontop/AlwaysOnTop/Settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ struct Settings
bool roundCornersEnabled = true;
bool blockInGameMode = true;
bool frameAccentColor = true;
int frameThickness = 15;
int frameThickness = 4;
int frameOpacity = 100;
COLORREF frameColor = RGB(0, 173, 239);
std::vector<std::wstring> excludedApps{};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public class AlwaysOnTopProperties
public static readonly HotkeySettings DefaultDecreaseOpacityHotkeyValue = new HotkeySettings(true, true, false, false, 0xBD);
public const bool DefaultFrameEnabled = true;
public const bool DefaultShowInSystemMenu = false;
public const int DefaultFrameThickness = 15;
public const int DefaultFrameThickness = 4;
public const string DefaultFrameColor = "#0099cc";
public const bool DefaultFrameAccentColor = true;
public const int DefaultFrameOpacity = 100;
Expand Down
Loading