Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[conhost] Fix WM_GETDPISCALEDSIZE handler, use provided size instead of current window size #18268

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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 src/cascadia/ut_app/TerminalApp.Unit.Tests.manifest
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
UI Variable, GH #12452 -->
<maxversiontested Id="10.0.22000.0"/>
<maxversiontested Id="10.0.22621.0"/>
<maxversiontested Id="10.0.26100.0"/>
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
Expand Down
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
97 changes: 60 additions & 37 deletions src/interactivity/win32/windowproc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -244,43 +244,12 @@ 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();
Expand Down Expand Up @@ -884,6 +853,60 @@ 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;
}
Comment on lines +871 to +875
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason you chose to replace the previous

const auto coordFontProposed = SUCCEEDED(hr) ? fiProposed.GetSize() : til::size{ 1, 1 };

?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the question about handling cases where getting the speculative font fails? If we cannot resolve the new font size, the new version will return false from WM_GETDPISCALEDSIZE, instead of filling in a size. This falls back to default processing, window size linearly scaled for dpi change.

til::size fontSizeNew = fontInfoNew.GetSize();

// The provided size is the window rect, which includes non-client area
// (caption bars, resize borders, scroll bars, etc). We want to scale the
// client area separately from the non-client area. The client area will be
// scaled using the new/old font sizes, so that the size of the grid (rows/
// columns) does not change.

// Subtract the size of the window's current non-client area from the
// provided size. This 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 new/old font sizes.
pSizeNew->cx = MulDiv(pSizeNew->cx, fontSizeNew.width, fontSizeCurrent.width);
pSizeNew->cy = MulDiv(pSizeNew->cy, fontSizeNew.height, fontSizeCurrent.height);

// Add the size of the non-client area at the new DPI to the final size,
// getting the new window rect (the output of this function).
rc = { 0, 0, pSizeNew->cx, pSizeNew->cy };
s_ExpandRectByNonClientSize(hwnd, dpiNew, &rc);

// Write the final size to the out parameter.
// If not Maximized/Arranged (snapped), this will determine the size of the
// rect in the WM_DPICHANGED message. Otherwise, the provided size is the
// normal position (restored, last non-Maximized/Arranged).
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
Loading