/* * MIT License * * Copyright (C) 2021-2023 by wangwenx190 (Yuhang Zhao) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #ifndef SM_CYPADDEDBORDER # define SM_CYPADDEDBORDER SM_CXPADDEDBORDER #endif #define DECLARE_DPI_ENTRY(DPR) \ UINT(std::round(double(USER_DEFAULT_SCREEN_DPI) * double(DPR))), double(DPR) static constexpr const wchar_t WINDOW_CLASS_NAME[] = L"org.wangwenx190.DpiTester.WindowClass"; static constexpr const wchar_t DEFAULT_STRUCT_NAME[] = L"SYSTEM_METRIC"; static constexpr const wchar_t DEFAULT_VARIABLE_NAME[] = L"SYSTEM_METRIC_TABLE"; static constexpr const wchar_t HASH_STD_NAME[] = L"std::unordered_map"; static constexpr const wchar_t HASH_QT_NAME[] = L"QHash"; static constexpr const wchar_t OPT_STRUCT_NAME[] = L"struct-name"; static constexpr const wchar_t OPT_VARIABLE_NAME[] = L"variable-name"; static constexpr const wchar_t OPT_ENABLE_QT[] = L"enable-qt"; static const std::unordered_map SYSTEM_METRIC_TABLE = { {L"SM_CXSCREEN", SM_CXSCREEN}, {L"SM_CYSCREEN", SM_CYSCREEN}, {L"SM_CXVSCROLL", SM_CXVSCROLL}, {L"SM_CYHSCROLL", SM_CYHSCROLL}, {L"SM_CYCAPTION", SM_CYCAPTION}, {L"SM_CXBORDER", SM_CXBORDER}, {L"SM_CYBORDER", SM_CYBORDER}, {L"SM_CXDLGFRAME", SM_CXDLGFRAME}, {L"SM_CYDLGFRAME", SM_CYDLGFRAME}, {L"SM_CYVTHUMB", SM_CYVTHUMB}, {L"SM_CXHTHUMB", SM_CXHTHUMB}, {L"SM_CXICON", SM_CXICON}, {L"SM_CYICON", SM_CYICON}, {L"SM_CXCURSOR", SM_CXCURSOR}, {L"SM_CYCURSOR", SM_CYCURSOR}, {L"SM_CYMENU", SM_CYMENU}, {L"SM_CXFULLSCREEN", SM_CXFULLSCREEN}, {L"SM_CYFULLSCREEN", SM_CYFULLSCREEN}, {L"SM_CYKANJIWINDOW", SM_CYKANJIWINDOW}, {L"SM_MOUSEPRESENT", SM_MOUSEPRESENT}, {L"SM_CYVSCROLL", SM_CYVSCROLL}, {L"SM_CXHSCROLL", SM_CXHSCROLL}, {L"SM_DEBUG", SM_DEBUG}, {L"SM_SWAPBUTTON", SM_SWAPBUTTON}, {L"SM_RESERVED1", SM_RESERVED1}, {L"SM_RESERVED2", SM_RESERVED2}, {L"SM_RESERVED3", SM_RESERVED3}, {L"SM_RESERVED4", SM_RESERVED4}, {L"SM_CXMIN", SM_CXMIN}, {L"SM_CYMIN", SM_CYMIN}, {L"SM_CXSIZE", SM_CXSIZE}, {L"SM_CYSIZE", SM_CYSIZE}, {L"SM_CXFRAME", SM_CXFRAME}, {L"SM_CYFRAME", SM_CYFRAME}, {L"SM_CXMINTRACK", SM_CXMINTRACK}, {L"SM_CYMINTRACK", SM_CYMINTRACK}, {L"SM_CXDOUBLECLK", SM_CXDOUBLECLK}, {L"SM_CYDOUBLECLK", SM_CYDOUBLECLK}, {L"SM_CXICONSPACING", SM_CXICONSPACING}, {L"SM_CYICONSPACING", SM_CYICONSPACING}, {L"SM_MENUDROPALIGNMENT", SM_MENUDROPALIGNMENT}, {L"SM_PENWINDOWS", SM_PENWINDOWS}, {L"SM_DBCSENABLED", SM_DBCSENABLED}, {L"SM_CMOUSEBUTTONS", SM_CMOUSEBUTTONS}, {L"SM_CXFIXEDFRAME", SM_CXFIXEDFRAME}, {L"SM_CYFIXEDFRAME", SM_CYFIXEDFRAME}, {L"SM_CXSIZEFRAME", SM_CXSIZEFRAME}, {L"SM_CYSIZEFRAME", SM_CYSIZEFRAME}, {L"SM_SECURE", SM_SECURE}, {L"SM_CXEDGE", SM_CXEDGE}, {L"SM_CYEDGE", SM_CYEDGE}, {L"SM_CXMINSPACING", SM_CXMINSPACING}, {L"SM_CYMINSPACING", SM_CYMINSPACING}, {L"SM_CXSMICON", SM_CXSMICON}, {L"SM_CYSMICON", SM_CYSMICON}, {L"SM_CYSMCAPTION", SM_CYSMCAPTION}, {L"SM_CXSMSIZE", SM_CXSMSIZE}, {L"SM_CYSMSIZE", SM_CYSMSIZE}, {L"SM_CXMENUSIZE", SM_CXMENUSIZE}, {L"SM_CYMENUSIZE", SM_CYMENUSIZE}, {L"SM_ARRANGE", SM_ARRANGE}, {L"SM_CXMINIMIZED", SM_CXMINIMIZED}, {L"SM_CYMINIMIZED", SM_CYMINIMIZED}, {L"SM_CXMAXTRACK", SM_CXMAXTRACK}, {L"SM_CYMAXTRACK", SM_CYMAXTRACK}, {L"SM_CXMAXIMIZED", SM_CXMAXIMIZED}, {L"SM_CYMAXIMIZED", SM_CYMAXIMIZED}, {L"SM_NETWORK", SM_NETWORK}, {L"SM_CLEANBOOT", SM_CLEANBOOT}, {L"SM_CXDRAG", SM_CXDRAG}, {L"SM_CYDRAG", SM_CYDRAG}, {L"SM_SHOWSOUNDS", SM_SHOWSOUNDS}, {L"SM_CXMENUCHECK", SM_CXMENUCHECK}, {L"SM_CYMENUCHECK", SM_CYMENUCHECK}, {L"SM_SLOWMACHINE", SM_SLOWMACHINE}, {L"SM_MIDEASTENABLED", SM_MIDEASTENABLED}, {L"SM_MOUSEWHEELPRESENT", SM_MOUSEWHEELPRESENT}, {L"SM_XVIRTUALSCREEN", SM_XVIRTUALSCREEN}, {L"SM_YVIRTUALSCREEN", SM_YVIRTUALSCREEN}, {L"SM_CXVIRTUALSCREEN", SM_CXVIRTUALSCREEN}, {L"SM_CYVIRTUALSCREEN", SM_CYVIRTUALSCREEN}, {L"SM_CMONITORS", SM_CMONITORS}, {L"SM_SAMEDISPLAYFORMAT", SM_SAMEDISPLAYFORMAT}, {L"SM_IMMENABLED", SM_IMMENABLED}, {L"SM_CXFOCUSBORDER", SM_CXFOCUSBORDER}, {L"SM_CYFOCUSBORDER", SM_CYFOCUSBORDER}, {L"SM_TABLETPC", SM_TABLETPC}, {L"SM_MEDIACENTER", SM_MEDIACENTER}, {L"SM_STARTER", SM_STARTER}, {L"SM_SERVERR2", SM_SERVERR2}, {L"SM_MOUSEHORIZONTALWHEELPRESENT", SM_MOUSEHORIZONTALWHEELPRESENT}, {L"SM_CXPADDEDBORDER", SM_CXPADDEDBORDER}, {L"SM_CYPADDEDBORDER", SM_CYPADDEDBORDER}, {L"SM_DIGITIZER", SM_DIGITIZER}, {L"SM_MAXIMUMTOUCHES", SM_MAXIMUMTOUCHES}, {L"SM_CMETRICS", SM_CMETRICS}, {L"SM_REMOTESESSION", SM_REMOTESESSION}, {L"SM_SHUTTINGDOWN", SM_SHUTTINGDOWN}, {L"SM_REMOTECONTROL", SM_REMOTECONTROL}, {L"SM_CARETBLINKINGENABLED", SM_CARETBLINKINGENABLED}, {L"SM_CONVERTIBLESLATEMODE", SM_CONVERTIBLESLATEMODE}, {L"SM_SYSTEMDOCKED", SM_SYSTEMDOCKED} }; static const struct { const UINT DotsPerInch = 0; const double DevicePixelRatio = 0.0; } DPI_TABLE[] = { DECLARE_DPI_ENTRY(1.00), // 100% DECLARE_DPI_ENTRY(1.20), // 120% DECLARE_DPI_ENTRY(1.25), // 125% DECLARE_DPI_ENTRY(1.40), // 140% DECLARE_DPI_ENTRY(1.50), // 150% DECLARE_DPI_ENTRY(1.60), // 160% DECLARE_DPI_ENTRY(1.75), // 175% DECLARE_DPI_ENTRY(1.80), // 180% DECLARE_DPI_ENTRY(2.00), // 200% DECLARE_DPI_ENTRY(2.25), // 225% DECLARE_DPI_ENTRY(2.50), // 250% DECLARE_DPI_ENTRY(3.00), // 300% DECLARE_DPI_ENTRY(3.50), // 350% DECLARE_DPI_ENTRY(4.00), // 400% DECLARE_DPI_ENTRY(4.50), // 450% DECLARE_DPI_ENTRY(5.00) // 500% }; static const auto DPI_COUNT = int(std::size(DPI_TABLE)); struct HiDPI { static inline decltype(&::GetDpiForWindow) GetDpiForWindowPtr = nullptr; static inline decltype(&::GetSystemMetricsForDpi) GetSystemMetricsForDpiPtr = nullptr; static inline decltype(&::GetDpiForMonitor) GetDpiForMonitorPtr = nullptr; static inline HWND FakeWindow = nullptr; static void Initialize(); static void Cleanup(); private: static inline bool inited = false; static inline HMODULE user32 = nullptr; static inline HMODULE shcore = nullptr; }; void HiDPI::Initialize() { if (inited) { return; } inited = true; user32 = LoadLibraryExW(L"user32", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); if (user32) { GetDpiForWindowPtr = reinterpret_cast(GetProcAddress(user32, "GetDpiForWindow")); GetSystemMetricsForDpiPtr = reinterpret_cast(GetProcAddress(user32, "GetSystemMetricsForDpi")); } else { std::wcerr << L"Failed to retrieve the handle of the USER32.DLL." << std::endl; } shcore = LoadLibraryExW(L"shcore", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); if (shcore) { GetDpiForMonitorPtr = reinterpret_cast(GetProcAddress(shcore, "GetDpiForMonitor")); } else { std::wcerr << L"Failed to retrieve the handle of the SHCORE.DLL." << std::endl; } if (const HINSTANCE instance = GetModuleHandleW(nullptr)) { WNDCLASSEXW wcex; SecureZeroMemory(&wcex, sizeof(wcex)); wcex.cbSize = sizeof(wcex); wcex.lpfnWndProc = DefWindowProcW; wcex.hInstance = instance; wcex.lpszClassName = WINDOW_CLASS_NAME; if (RegisterClassExW(&wcex) != INVALID_ATOM) { FakeWindow = CreateWindowExW(0, WINDOW_CLASS_NAME, nullptr, WS_OVERLAPPEDWINDOW, 0, 0, 0, 0, nullptr, nullptr, instance, nullptr); if (!FakeWindow) { std::wcerr << L"Failed to create the window." << std::endl; } } else { std::wcerr << L"Failed to register the window class." << std::endl; } } else { std::wcerr << L"Failed to retrieve the handle of the current instance." << std::endl; } } void HiDPI::Cleanup() { if (!inited) { return; } inited = false; GetDpiForWindowPtr = nullptr; GetSystemMetricsForDpiPtr = nullptr; GetDpiForMonitorPtr = nullptr; if (FakeWindow) { DestroyWindow(FakeWindow); UnregisterClassW(WINDOW_CLASS_NAME, GetModuleHandleW(nullptr)); FakeWindow = nullptr; } if (shcore) { FreeLibrary(shcore); shcore = nullptr; } if (user32) { FreeLibrary(user32); user32 = nullptr; } } [[nodiscard]] static inline UINT GetCurrentDPIForPrimaryScreen() { HiDPI::Initialize(); if (const HMONITOR hMonitor = MonitorFromWindow(GetDesktopWindow(), MONITOR_DEFAULTTOPRIMARY)) { if (HiDPI::GetDpiForMonitorPtr) { UINT dpiX = 0, dpiY = 0; const HRESULT hr = HiDPI::GetDpiForMonitorPtr(hMonitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY); if (SUCCEEDED(hr) && (dpiX > 0) && (dpiY > 0)) { return dpiX; } else { std::wcerr << L"GetDpiForMonitor() failed." << std::endl; } } MONITORINFOEXW monitorInfo; SecureZeroMemory(&monitorInfo, sizeof(monitorInfo)); monitorInfo.cbSize = sizeof(monitorInfo); GetMonitorInfoW(hMonitor, &monitorInfo); if (const HDC hdc = CreateDCW(monitorInfo.szDevice, monitorInfo.szDevice, nullptr, nullptr)) { const int dpiX = GetDeviceCaps(hdc, LOGPIXELSX); const int dpiY = GetDeviceCaps(hdc, LOGPIXELSY); DeleteDC(hdc); if ((dpiX > 0) && (dpiY > 0)) { return dpiX; } else { std::wcerr << L"Failed to retrieve the primary screen's DPI." << std::endl; } } else { std::wcerr << L"Failed to create DC for the primary monitor." << std::endl; } } else { std::wcerr << L"Failed to retrieve the primary monitor." << std::endl; } if (const HDC hdc = GetDC(nullptr)) { const int dpiX = GetDeviceCaps(hdc, LOGPIXELSX); const int dpiY = GetDeviceCaps(hdc, LOGPIXELSY); ReleaseDC(nullptr, hdc); if ((dpiX > 0) && (dpiY > 0)) { return dpiX; } else { std::wcerr << L"Failed to retrieve the primary screen's DPI." << std::endl; } } else { std::wcerr << L"Failed to retrieve the primary screen's DC." << std::endl; } return 0; } [[nodiscard]] static inline UINT GetCurrentDPIForWindow() { HiDPI::Initialize(); if (HiDPI::GetDpiForWindowPtr) { return HiDPI::GetDpiForWindowPtr(HiDPI::FakeWindow); } return 0; } [[nodiscard]] static inline UINT GetCurrentDPI() { UINT dpi = GetCurrentDPIForWindow(); if (dpi == 0) { std::wcerr << L"Failed to retrieve the DPI from the window, trying to fetch from the primary screen instead..." << std::endl; dpi = GetCurrentDPIForPrimaryScreen(); if (dpi == 0) { std::wcerr << L"Failed to retrieve the DPI from the primary screen, using the default DPI value instead..." << std::endl; dpi = USER_DEFAULT_SCREEN_DPI; } } return dpi; } [[nodiscard]] static inline int GetSystemMetricsForDpi2(const int index, const UINT dpi) { HiDPI::Initialize(); if (HiDPI::GetSystemMetricsForDpiPtr) { return HiDPI::GetSystemMetricsForDpiPtr(index, dpi); } const double dpr = double(GetCurrentDPI()) / double(USER_DEFAULT_SCREEN_DPI); const double result = double(GetSystemMetrics(index)) / dpr; return int(std::round(result)); } [[nodiscard]] static inline std::wstring to_lower(std::wstring str) { if (str.empty()) { return {}; } std::transform(str.begin(), str.end(), str.begin(), std::towlower); return str; } EXTERN_C int WINAPI wmain(int argc, wchar_t *argv[]) { std::setlocale(LC_ALL, "en_US.UTF-8"); HiDPI::Initialize(); std::unordered_map options = {}; std::vector metrics = {}; if (argc > 1) { bool skip = false; for (int i = 1; i != argc; ++i) { if (skip) { skip = false; continue; } const std::wstring arg = argv[i]; if (arg.starts_with(L"SM_CX") || arg.starts_with(L"SM_CY")) { if (SYSTEM_METRIC_TABLE.contains(arg)) { if (std::find(std::begin(metrics), std::end(metrics), arg) == std::end(metrics)) { metrics.push_back(arg); } else { std::wcerr << L"Duplicated system metric value: " << arg << std::endl; } } else { std::wcerr << L"Unrecognized system metric value: " << arg << std::endl; } } else if (arg.starts_with(L'/') || arg.starts_with(L"--")) { const int length = arg.starts_with(L'/') ? 1 : 2; std::wstring option = std::wstring(arg).erase(0, length); std::wstring param = {}; const std::wstring::size_type pos = option.find_first_of(L'='); if (pos == std::wstring::npos) { const int index = i + 1; if (index < argc) { skip = true; param = argv[index]; } } else { param = option.substr(pos + 1); option = option.substr(0, pos); } if (options.contains(option)) { std::wcerr << L"Duplicated option: " << option << std::endl; } else { options.insert({option, param}); } } else { std::wcerr << L"Unrecognized parameter: " << arg << std::endl; } } } if (metrics.empty()) { for (auto &&[key, value] : std::as_const(SYSTEM_METRIC_TABLE)) { if (key.starts_with(L"SM_CX") || key.starts_with(L"SM_CY")) { metrics.push_back(key); } } } const auto metrics_count = int(metrics.size()); const auto struct_name = [&options]() -> std::wstring { if (options.contains(OPT_STRUCT_NAME)) { const std::wstring name = options.at(OPT_STRUCT_NAME); if (!name.empty()) { return name; } } return DEFAULT_STRUCT_NAME; }(); const auto variable_name = [&options]() -> std::wstring { if (options.contains(OPT_VARIABLE_NAME)) { const std::wstring name = options.at(OPT_VARIABLE_NAME); if (!name.empty()) { return name; } } return DEFAULT_VARIABLE_NAME; }(); const bool enable_qt = options.contains(OPT_ENABLE_QT); const std::wstring hash_name = enable_qt ? HASH_QT_NAME : HASH_STD_NAME; std::wstring text = {}; text += L"struct " + struct_name + L"\n{\n"; for (int i = 0; i != DPI_COUNT; ++i) { const auto entry = DPI_TABLE[i]; const auto percent = int(std::round(entry.DevicePixelRatio * double(100))); text += L" int DPI_" + std::to_wstring(entry.DotsPerInch) + L" = 0;"; text += L" // " + std::to_wstring(percent) + L"%. The scale factor for the device is " + std::to_wstring(entry.DevicePixelRatio) + L"x.\n"; if (i == (DPI_COUNT - 1)) { text += L"};\n"; } } text += L'\n'; text += L"static const " + hash_name + L" " + variable_name + L" =\n{\n"; for (int i = 0; i != metrics_count; ++i) { const std::wstring sm = metrics.at(i); text += L" {" + sm + L", {"; for (int j = 0; j != DPI_COUNT; ++j) { const int index = SYSTEM_METRIC_TABLE.at(sm); const int value = GetSystemMetricsForDpi2(index, DPI_TABLE[j].DotsPerInch); text += std::to_wstring(value); if (j == (DPI_COUNT - 1)) { text += L"}}"; } else { text += L", "; } } if (i != (metrics_count - 1)) { text += L','; } text += L'\n'; } text += L"};"; std::wcout << text << std::endl; HiDPI::Cleanup(); return 0; }