From 8c20514b8de4e4508345081ca481a618dbc614e6 Mon Sep 17 00:00:00 2001 From: Arthur Liberman Date: Mon, 29 Mar 2021 18:31:13 +0300 Subject: [PATCH] Windows 10: add per monitor DPI awareness support. On a system with 2 or more displays with different DPI settings, moving the PuTTY window from one display to another will make Windows resize the window using its "bitmap" strategy, stretching/compressing the text, making it fuzzy and harder to read. This change makes PuTTY resize its window and font size to accurately fit the DPI of the display it is on. We process the WM_DPICHANGED message, saving the new DPI, window size and position. We proceed to then reset the window, recreating the fonts using the new DPI and calculate the new window size and position based on the new font size, user display options (ie. with/without scrollbar) and the suggested window position provided by Windows. The suggested window size is usually not a perfect fit, therefore we must add a small offset to the new window position in order to avoid issues with repeated DPI changes while dragging the window from one display to another. --- windows/putty.mft | 4 +++ windows/puttytel.mft | 4 +++ windows/window.c | 86 +++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 93 insertions(+), 1 deletion(-) diff --git a/windows/putty.mft b/windows/putty.mft index fdd000d2..94b7be0c 100644 --- a/windows/putty.mft +++ b/windows/putty.mft @@ -26,6 +26,10 @@ true + + PerMonitorV2 + diff --git a/windows/puttytel.mft b/windows/puttytel.mft index 81b4ddaa..0cc5891d 100644 --- a/windows/puttytel.mft +++ b/windows/puttytel.mft @@ -26,6 +26,10 @@ true + + PerMonitorV2 + diff --git a/windows/window.c b/windows/window.c index cc4bba8b..aa0be484 100644 --- a/windows/window.c +++ b/windows/window.c @@ -82,6 +82,14 @@ #define WHEEL_DELTA 120 #endif +/* DPI awareness support */ +#ifndef WM_DPICHANGED +#define WM_DPICHANGED 0x02E0 +#define WM_DPICHANGED_BEFOREPARENT 0x02E2 +#define WM_DPICHANGED_AFTERPARENT 0x02E3 +#define WM_GETDPISCALEDSIZE 0x02E4 +#endif + /* VK_PACKET, used to send Unicode characters in WM_KEYDOWNs */ #ifndef VK_PACKET #define VK_PACKET 0xE7 @@ -94,6 +102,7 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, unsigned char *output); static void init_palette(void); static void init_fonts(int, int); +static void init_dpi_info(void); static void another_font(int); static void deinit_fonts(void); static void set_input_locale(HKL); @@ -193,6 +202,16 @@ static LPLOGPALETTE logpal; bool tried_pal = false; COLORREF colorref_modifier = 0; +enum MONITOR_DPI_TYPE { MDT_EFFECTIVE_DPI, MDT_ANGULAR_DPI, MDT_RAW_DPI, MDT_DEFAULT }; +DECL_WINDOWS_FUNCTION(static, HRESULT, GetDpiForMonitor, (HMONITOR hmonitor, enum MONITOR_DPI_TYPE dpiType, UINT *dpiX, UINT *dpiY)); +DECL_WINDOWS_FUNCTION(static, HRESULT, GetSystemMetricsForDpi, (int nIndex, UINT dpi)); +DECL_WINDOWS_FUNCTION(static, HRESULT, AdjustWindowRectExForDpi, (LPRECT lpRect, DWORD dwStyle, BOOL bMenu, DWORD dwExStyle, UINT dpi)); + +static struct _dpi_info { + POINT cur_dpi; + RECT new_wnd_rect; +} dpi_info; + static HBITMAP caretbm; static int dbltime, lasttime, lastact; @@ -713,6 +732,8 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) wgs.term_hwnd = CreateWindowExW( exwinmode, uappname, uappname, winmode, CW_USEDEFAULT, CW_USEDEFAULT, guess_width, guess_height, NULL, NULL, inst, NULL); + memset(&dpi_info, 0, sizeof(struct _dpi_info)); + init_dpi_info(); sfree(uappname); } @@ -1341,6 +1362,30 @@ static int get_font_width(HDC hdc, const TEXTMETRIC *tm) return ret; } +static void init_dpi_info(void) +{ + if (dpi_info.cur_dpi.x == 0 || dpi_info.cur_dpi.y == 0) { + if (p_GetDpiForMonitor) { + UINT dpiX, dpiY; + HMONITOR currentMonitor = MonitorFromWindow( + wgs.term_hwnd, MONITOR_DEFAULTTOPRIMARY); + if (p_GetDpiForMonitor(currentMonitor, MDT_EFFECTIVE_DPI, + &dpiX, &dpiY) == S_OK) { + dpi_info.cur_dpi.x = (int)dpiX; + dpi_info.cur_dpi.y = (int)dpiY; + } + } + + /* Fall back to system DPI */ + if (dpi_info.cur_dpi.x == 0 || dpi_info.cur_dpi.y == 0) { + HDC hdc = GetDC(wgs.term_hwnd); + dpi_info.cur_dpi.x = GetDeviceCaps(hdc, LOGPIXELSX); + dpi_info.cur_dpi.y = GetDeviceCaps(hdc, LOGPIXELSY); + ReleaseDC(wgs.term_hwnd, hdc); + } + } +} + /* * Initialise all the fonts we will need initially. There may be as many as * three or as few as one. The other (potentially) twenty-one fonts are done @@ -1397,7 +1442,7 @@ static void init_fonts(int pick_width, int pick_height) font_height = font->height; if (font_height > 0) { font_height = - -MulDiv(font_height, GetDeviceCaps(hdc, LOGPIXELSY), 72); + -MulDiv(font_height, dpi_info.cur_dpi.y, 72); } } font_width = pick_width; @@ -1794,6 +1839,35 @@ static void reset_window(int reinit) { return; } + /* Resize window after DPI change */ + if (reinit == 3 && p_GetSystemMetricsForDpi && p_AdjustWindowRectExForDpi) { + RECT rect; + rect.left = rect.top = 0; + rect.right = (font_width * term->cols); + if (conf_get_bool(conf, CONF_scrollbar)) + rect.right += p_GetSystemMetricsForDpi(SM_CXVSCROLL, + dpi_info.cur_dpi.x); + rect.bottom = (font_height * term->rows); + p_AdjustWindowRectExForDpi( + &rect, GetWindowLongPtr(wgs.term_hwnd, GWL_STYLE), + FALSE, GetWindowLongPtr(wgs.term_hwnd, GWL_EXSTYLE), + dpi_info.cur_dpi.x); + rect.right += (window_border * 2); + rect.bottom += (window_border * 2); + OffsetRect(&dpi_info.new_wnd_rect, + ((dpi_info.new_wnd_rect.right - dpi_info.new_wnd_rect.left) - + (rect.right - rect.left)) / 2, + ((dpi_info.new_wnd_rect.bottom - dpi_info.new_wnd_rect.top) - + (rect.bottom - rect.top)) / 2); + SetWindowPos(wgs.term_hwnd, NULL, + dpi_info.new_wnd_rect.left, dpi_info.new_wnd_rect.top, + rect.right - rect.left, rect.bottom - rect.top, + SWP_NOZORDER); + + InvalidateRect(wgs.term_hwnd, NULL, true); + return; + } + /* Hmm, a force re-init means we should ignore the current window * so we resize to the default font size. */ @@ -3031,6 +3105,12 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, } sys_cursor_update(); return 0; + case WM_DPICHANGED: + dpi_info.cur_dpi.x = LOWORD(wParam); + dpi_info.cur_dpi.y = HIWORD(wParam); + dpi_info.new_wnd_rect = *(RECT*)(lParam); + reset_window(3); + return 0; case WM_VSCROLL: switch (LOWORD(wParam)) { case SB_BOTTOM: @@ -3974,9 +4054,13 @@ static void init_winfuncs(void) { HMODULE user32_module = load_system32_dll("user32.dll"); HMODULE winmm_module = load_system32_dll("winmm.dll"); + HMODULE shcore_module = load_system32_dll("shcore.dll"); GET_WINDOWS_FUNCTION(user32_module, FlashWindowEx); GET_WINDOWS_FUNCTION(user32_module, ToUnicodeEx); GET_WINDOWS_FUNCTION_PP(winmm_module, PlaySound); + GET_WINDOWS_FUNCTION_NO_TYPECHECK(shcore_module, GetDpiForMonitor); + GET_WINDOWS_FUNCTION_NO_TYPECHECK(user32_module, GetSystemMetricsForDpi); + GET_WINDOWS_FUNCTION_NO_TYPECHECK(user32_module, AdjustWindowRectExForDpi); } /*