Skip to content

Commit

Permalink
rewrite conhost WM_GETDPISCALEDSIZE handler
Browse files Browse the repository at this point in the history
  • Loading branch information
Evan Koschik committed Dec 1, 2024
1 parent 9243104 commit 2bfd71c
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 38 deletions.
24 changes: 24 additions & 0 deletions src/interactivity/win32/window.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -964,6 +964,30 @@ void Window::s_CalculateWindowRect(const til::size coordWindowInChars,
prectWindow->bottom = prectWindow->top + rectProposed.height();
}

// Expands a rect by the size of the non-client area (caption bar, resize borders,
// scroll bars, etc), which depends on the window styles and DPI
void Window:: s_ExpandRectByNonClientSize(HWND const hWnd,
UINT dpi,
_Inout_ til::rect* const prectWindow)
{
DWORD dwStyle = GetWindowStyle(hWnd);
DWORD dwExStyle = GetWindowExStyle(hWnd);
BOOL fMenu = FALSE;

ServiceLocator::LocateWindowMetrics<WindowMetrics>()->AdjustWindowRectEx(
prectWindow, dwStyle, fMenu, dwExStyle, dpi);

// Note: AdjustWindowRectEx does not account for scroll bars :(.
if (WI_IsFlagSet(dwStyle, WS_HSCROLL))
{
prectWindow->bottom += ServiceLocator::LocateHighDpiApi<WindowDpiApi>()->GetSystemMetricsForDpi(SM_CYHSCROLL, dpi);
}
if (WI_IsFlagSet(dwStyle, WS_VSCROLL))
{
prectWindow->bottom += ServiceLocator::LocateHighDpiApi<WindowDpiApi>()->GetSystemMetricsForDpi(SM_CXVSCROLL, dpi);
}
}

til::rect Window::GetWindowRect() const noexcept
{
RECT rc{};
Expand Down
4 changes: 4 additions & 0 deletions src/interactivity/win32/window.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ namespace Microsoft::Console::Interactivity::Win32
void _HandleDrop(const WPARAM wParam) const;
[[nodiscard]] HRESULT _HandlePaint() const;
void _HandleWindowPosChanged(const LPARAM lParam);
bool _HandleGetDpiScaledSize(UINT dpiNew, _Inout_ SIZE* pSizeNew) const;

// Accessibility/UI Automation
[[nodiscard]] LRESULT _HandleGetObject(const HWND hwnd,
Expand Down Expand Up @@ -173,6 +174,9 @@ namespace Microsoft::Console::Interactivity::Win32
const til::size coordBufferSize,
_In_opt_ HWND const hWnd,
_Inout_ til::rect* const prectWindow);
static void s_ExpandRectByNonClientSize(HWND const hWnd,
UINT dpi,
_Inout_ til::rect* const prectWindow);

static void s_ReinitializeFontsForDPIChange();

Expand Down
93 changes: 55 additions & 38 deletions src/interactivity/win32/windowproc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -244,45 +244,13 @@ static constexpr TsfDataProvider s_tsfDataProvider;

case WM_GETDPISCALEDSIZE:
{
// This message will send us the DPI we're about to be changed to.
// Our goal is to use it to try to figure out the Window Rect that we'll need at that DPI to maintain
// the same client rendering that we have now.

// First retrieve the new DPI and the current DPI.
const auto dpiProposed = (WORD)wParam;

// Now we need to get what the font size *would be* if we had this new DPI. We need to ask the renderer about that.
const auto& fiCurrent = ScreenInfo.GetCurrentFont();
FontInfoDesired fiDesired(fiCurrent);
FontInfo fiProposed(L"", 0, 0, { 0, 0 }, 0);

const auto hr = g.pRender->GetProposedFont(dpiProposed, fiDesired, fiProposed);
// fiProposal will be updated by the renderer for this new font.
// GetProposedFont can fail if there's no render engine yet.
// This can happen if we're headless.
// Just assume that the font is 1x1 in that case.
const auto coordFontProposed = SUCCEEDED(hr) ? fiProposed.GetSize() : til::size{ 1, 1 };

// Then from that font size, we need to calculate the client area.
// Then from the client area we need to calculate the window area (using the proposed DPI scalar here as well.)

// Retrieve the additional parameters we need for the math call based on the current window & buffer properties.
const auto viewport = ScreenInfo.GetViewport();
auto coordWindowInChars = viewport.Dimensions();

const auto coordBufferSize = ScreenInfo.GetTextBuffer().GetSize().Dimensions();

// Now call the math calculation for our proposed size.
til::rect rectProposed;
s_CalculateWindowRect(coordWindowInChars, dpiProposed, coordFontProposed, coordBufferSize, hWnd, &rectProposed);

// Prepare where we're going to keep our final suggestion.
const auto pSuggestionSize = (SIZE*)lParam;

pSuggestionSize->cx = rectProposed.width();
pSuggestionSize->cy = rectProposed.height();
SIZE* pSizeNew = (SIZE*)lParam;
UINT dpiNew = (WORD)wParam;
if (!_HandleGetDpiScaledSize(dpiNew, pSizeNew))
{
return FALSE;
}

// Format our final suggestion for consumption.
UnlockConsole();
return TRUE;
}
Expand Down Expand Up @@ -884,6 +852,55 @@ void Window::_HandleWindowPosChanged(const LPARAM lParam)
}
}

// WM_GETDPISCALEDSIZE is sent prior to the window changing DPI, allowing us to
// choose the size at the new DPI (overriding the default, linearly scaled).
//
// This is used to keep the rows and columns from changing when the DPI changes.
bool Window::_HandleGetDpiScaledSize(UINT dpiNew, _Inout_ SIZE* pSizeNew) const
{
// Get the current DPI and font size.
HWND hwnd = GetWindowHandle();
UINT dpiCurrent = ServiceLocator::LocateHighDpiApi<WindowDpiApi>()->GetDpiForWindow(hwnd);
const auto& fontInfoCurrent = GetScreenInfo().GetCurrentFont();
til::size fontSizeCurrent = fontInfoCurrent.GetSize();

// Scale the current font to the new DPI and get the new font size.
FontInfoDesired fontInfoDesired(fontInfoCurrent);
FontInfo fontInfoNew(L"", 0, 0, { 0, 0 }, 0);
if (!SUCCEEDED(ServiceLocator::LocateGlobals().pRender->GetProposedFont(
dpiNew, fontInfoDesired, fontInfoNew)))
{
return false;
}
til::size fontSizeNew = fontInfoNew.GetSize();

// The provided size is the window rect, which includes non-client area
// (caption bars, resize borders, scroll bars, etc). To scale the client
// area by the new/previous font sizes, the non-client area size is removed
// from the rect here and added back at the end.

// Expand an empty rect by the size of the current non-client area (using
// the window's current DPI). This gives us the size of the non client area.
// Reduce the provided size by the non-client area size, which gives us the
// new client area size at the previous DPI.
til::rect rc;
s_ExpandRectByNonClientSize(hwnd, dpiCurrent, &rc);
pSizeNew->cx -= rc.width();
pSizeNew->cy -= rc.height();

// Scale the size of the client rect by the change in font size.
pSizeNew->cx = MulDiv(pSizeNew->cx, fontSizeNew.width, fontSizeCurrent.width);
pSizeNew->cy = MulDiv(pSizeNew->cy, fontSizeNew.height, fontSizeCurrent.height);

// Expand the scaled client size by the non-client area (using the new DPI).
rc = { 0, 0, pSizeNew->cx, pSizeNew->cy };
s_ExpandRectByNonClientSize(hwnd, dpiNew, &rc);
pSizeNew->cx = rc.width();
pSizeNew->cy = rc.height();

return true;
}

// Routine Description:
// - This helper method for the window procedure will handle the WM_PAINT message
// - It will retrieve the invalid rectangle and dispatch that information to the attached renderer
Expand Down

0 comments on commit 2bfd71c

Please sign in to comment.