From a26df61c3363cae9ee2d0f9cac3d4eb38d4acb45 Mon Sep 17 00:00:00 2001 From: Yuhang Zhao <2546789017@qq.com> Date: Thu, 24 Nov 2022 10:38:21 +0800 Subject: [PATCH] win: fix multi-monitor bug, take 3 #141 #184 Signed-off-by: Yuhang Zhao <2546789017@qq.com> --- .../Core/framelesshelpercore_global.h | 18 +++--- include/FramelessHelper/Core/utils.h | 6 ++ src/core/framelesshelper_win.cpp | 63 ++++++++++++++----- src/core/utils.cpp | 32 ++++++++++ src/core/utils_win.cpp | 63 +++++++++++++++++++ 5 files changed, 156 insertions(+), 26 deletions(-) diff --git a/include/FramelessHelper/Core/framelesshelpercore_global.h b/include/FramelessHelper/Core/framelesshelpercore_global.h index 1c864a4..51da410 100644 --- a/include/FramelessHelper/Core/framelesshelpercore_global.h +++ b/include/FramelessHelper/Core/framelesshelpercore_global.h @@ -407,10 +407,10 @@ Q_ENUM_NS(WindowCornerStyle) struct VersionNumber { - const int major = 0; - const int minor = 0; - const int patch = 0; - const int tweak = 0; + int major = 0; + int minor = 0; + int patch = 0; + int tweak = 0; [[nodiscard]] friend constexpr bool operator==(const VersionNumber &lhs, const VersionNumber &rhs) noexcept { @@ -565,19 +565,19 @@ struct SystemParameters struct VersionInfo { - const int version = 0; + int version = 0; const char *version_str = nullptr; const char *commit = nullptr; const char *compileDateTime = nullptr; const char *compiler = nullptr; - const bool isDebug = false; - const bool isStatic = false; + bool isDebug = false; + bool isStatic = false; }; struct Dpi { - const quint32 x = 0; - const quint32 y = 0; + quint32 x = 0; + quint32 y = 0; }; #ifdef Q_OS_WINDOWS diff --git a/include/FramelessHelper/Core/utils.h b/include/FramelessHelper/Core/utils.h index 1fa7cda..eff6ba2 100644 --- a/include/FramelessHelper/Core/utils.h +++ b/include/FramelessHelper/Core/utils.h @@ -68,6 +68,7 @@ FRAMELESSHELPER_CORE_API void moveWindowToDesktopCenter( [[nodiscard]] FRAMELESSHELPER_CORE_API bool isBlurBehindWindowSupported(); FRAMELESSHELPER_CORE_API void registerThemeChangeNotification(); [[nodiscard]] FRAMELESSHELPER_CORE_API QColor getFrameBorderColor(const bool active); +[[nodiscard]] FRAMELESSHELPER_CORE_API qreal roundScaleFactor(const qreal factor); #ifdef Q_OS_WINDOWS [[nodiscard]] FRAMELESSHELPER_CORE_API bool isWindowsVersionOrGreater(const Global::WindowsVersion version); @@ -89,11 +90,16 @@ FRAMELESSHELPER_CORE_API void showSystemMenu( [[nodiscard]] FRAMELESSHELPER_CORE_API bool isHighContrastModeEnabled(); [[nodiscard]] FRAMELESSHELPER_CORE_API quint32 getPrimaryScreenDpi(const bool horizontal); [[nodiscard]] FRAMELESSHELPER_CORE_API quint32 getWindowDpi(const WId windowId, const bool horizontal); +[[nodiscard]] FRAMELESSHELPER_CORE_API quint32 getResizeBorderThicknessForDpi + (const bool horizontal, const quint32 dpi); [[nodiscard]] FRAMELESSHELPER_CORE_API quint32 getResizeBorderThickness(const WId windowId, const bool horizontal, const bool scaled); +[[nodiscard]] FRAMELESSHELPER_CORE_API quint32 getCaptionBarHeightForDpi(const quint32 dpi); [[nodiscard]] FRAMELESSHELPER_CORE_API quint32 getCaptionBarHeight(const WId windowId, const bool scaled); +[[nodiscard]] FRAMELESSHELPER_CORE_API quint32 getTitleBarHeightForDpi(const quint32 dpi); [[nodiscard]] FRAMELESSHELPER_CORE_API quint32 getTitleBarHeight(const WId windowId, const bool scaled); +[[nodiscard]] FRAMELESSHELPER_CORE_API quint32 getFrameBorderThicknessForDpi(const quint32 dpi); [[nodiscard]] FRAMELESSHELPER_CORE_API quint32 getFrameBorderThickness(const WId windowId, const bool scaled); FRAMELESSHELPER_CORE_API void maybeFixupQtInternals(const WId windowId); diff --git a/src/core/framelesshelper_win.cpp b/src/core/framelesshelper_win.cpp index 958daba..2d66d59 100644 --- a/src/core/framelesshelper_win.cpp +++ b/src/core/framelesshelper_win.cpp @@ -95,6 +95,7 @@ struct Win32HelperData SystemParameters params = {}; bool trackingMouse = false; WId fallbackTitleBarWindowId = 0; + Dpi dpi = {}; }; struct Win32Helper @@ -521,14 +522,14 @@ void FramelessHelperWin::addWindow(const SystemParameters ¶ms) } Win32HelperData data = {}; data.params = params; + data.dpi = {Utils::getWindowDpi(windowId, true), Utils::getWindowDpi(windowId, false)}; g_win32Helper()->data.insert(windowId, data); if (g_win32Helper()->nativeEventFilter.isNull()) { g_win32Helper()->nativeEventFilter.reset(new FramelessHelperWin); qApp->installNativeEventFilter(g_win32Helper()->nativeEventFilter.data()); } g_win32Helper()->mutex.unlock(); - const Dpi dpi = {Utils::getWindowDpi(windowId, true), Utils::getWindowDpi(windowId, false)}; - DEBUG.noquote() << "The DPI of window" << hwnd2str(windowId) << "is" << dpi; + DEBUG.noquote() << "The DPI of window" << hwnd2str(windowId) << "is" << data.dpi; // Some Qt internals have to be corrected. Utils::maybeFixupQtInternals(windowId); // Qt maintains a frame margin internally, we need to update it accordingly @@ -1123,28 +1124,56 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me const auto windowPos = reinterpret_cast(lParam); windowPos->flags |= SWP_NOCOPYBITS; } break; +#endif +#if ((QT_VERSION <= QT_VERSION_CHECK(6, 2, 6)) || ((QT_VERSION >= QT_VERSION_CHECK(6, 3, 0)) && (QT_VERSION <= QT_VERSION_CHECK(6, 4, 1)))) + case WM_GETDPISCALEDSIZE: { + // QtBase commit 2cfca7fd1911cc82a22763152c04c65bc05bc19a introduced a bug + // which caused the custom margins is ignored during the handling of the + // WM_GETDPISCALEDSIZE message, it was shipped with Qt 6.2.1 ~ 6.2.6 & 6.3 ~ 6.4.1. + // We workaround it by overriding the wrong handling directly. + RECT clientRect = {}; + if (GetClientRect(hWnd, &clientRect) == FALSE) { + WARNING << Utils::getSystemErrorMessage(kGetClientRect); + *result = FALSE; // Use the default linear DPI scaling provided by Windows. + return true; // Jump over Qt's wrong handling logic. + } + const QSizeF oldSize = {qreal(clientRect.right - clientRect.left), qreal(clientRect.bottom - clientRect.top)}; + const UINT oldDpi = data.dpi.x; + static constexpr const auto defaultDpi = qreal(USER_DEFAULT_SCREEN_DPI); + // We need to round the scale factor according to Qt's rounding policy. + const qreal oldDpr = Utils::roundScaleFactor(qreal(oldDpi) / defaultDpi); + const QSizeF unscaledSize = (oldSize / oldDpr); + const auto newDpi = UINT(wParam); + const qreal newDpr = Utils::roundScaleFactor(qreal(newDpi) / defaultDpi); + const QSizeF newSize = (unscaledSize * newDpr); + const auto suggestedSize = reinterpret_cast(lParam); + suggestedSize->cx = qRound(newSize.width()); + suggestedSize->cy = qRound(newSize.height()); + // If the window frame is visible, we need to expand the suggested size, currently + // it's pure client size, we need to add the frame size to it. Windows expects a + // full window size, including the window frame. + // If the window frame is not visible, the window size equals to the client size, + // the suggested size doesn't need further adjustments. + if (frameBorderVisible) { + const int frameSizeX = Utils::getResizeBorderThicknessForDpi(true, newDpi); + const int frameSizeY = Utils::getResizeBorderThicknessForDpi(false, newDpi); + suggestedSize->cx += (frameSizeX * 2); + suggestedSize->cy += frameSizeY; + } + *result = TRUE; // We have set our preferred window size, don't use the default linear DPI scaling. + return true; // Jump over Qt's wrong handling logic. + } #endif case WM_DPICHANGED: { const Dpi dpi = {UINT(LOWORD(wParam)), UINT(HIWORD(wParam))}; DEBUG.noquote() << "New DPI for window" << hwnd2str(hWnd) << "is" << dpi; + g_win32Helper()->mutex.lock(); + g_win32Helper()->data[windowId].dpi = dpi; + g_win32Helper()->mutex.unlock(); #if (QT_VERSION <= QT_VERSION_CHECK(6, 4, 1)) - const auto suggestedRect = *reinterpret_cast(lParam); - const int titleBarHeight = Utils::getTitleBarHeight(windowId, true); // We need to wait until Qt has handled this message, otherwise everything // we have done here will always be overwritten. - QTimer::singleShot(0, qApp, [data, hWnd, titleBarHeight, suggestedRect](){ // Copy the variables intentionally, otherwise they'll go out of scope when Qt finally use them. - // QtBase commit 2cfca7fd1911cc82a22763152c04c65bc05bc19a introduced a bug - // which caused the custom margins is ignored while handling the DPI change - // messages, it was shipped with Qt 6.2.1 ~ 6.2.6 & 6.3 ~ 6.4.1. We can - // workaround it by manually shrink the window size after Qt handles WM_DPICHANGED. -# if (((QT_VERSION >= QT_VERSION_CHECK(6, 2, 1)) && (QT_VERSION <= QT_VERSION_CHECK(6, 2, 6))) || (QT_VERSION >= QT_VERSION_CHECK(6, 3, 0))) - const int width = (suggestedRect.right - suggestedRect.left); - const int height = (suggestedRect.bottom - suggestedRect.top - titleBarHeight); - static constexpr const UINT flags = (SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOOWNERZORDER); - if (SetWindowPos(hWnd, nullptr, 0, 0, width, height, flags) == FALSE) { - WARNING << Utils::getSystemErrorMessage(kSetWindowPos); - } -# endif // (((QT_VERSION >= QT_VERSION_CHECK(6, 2, 1)) && (QT_VERSION <= QT_VERSION_CHECK(6, 2, 6))) || (QT_VERSION >= QT_VERSION_CHECK(6, 3, 0))) + QTimer::singleShot(0, qApp, [data](){ // Copy the variables intentionally, otherwise they'll go out of scope when Qt finally use them. // Sync the internal window frame margins with the latest DPI, otherwise // we will get wrong window sizes after the DPI change. Utils::updateInternalWindowFrameMargins(data.params.getWindowHandle(), true); diff --git a/src/core/utils.cpp b/src/core/utils.cpp index e9fde65..7d26d77 100644 --- a/src/core/utils.cpp +++ b/src/core/utils.cpp @@ -294,4 +294,36 @@ bool Utils::shouldAppsUseDarkMode() #endif } +qreal Utils::roundScaleFactor(const qreal factor) +{ + Q_ASSERT(factor > 0); + if (factor <= 0) { + return 1; + } +#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) + static const auto policy = QGuiApplication::highDpiScaleFactorRoundingPolicy(); + switch (policy) { + case Qt::HighDpiScaleFactorRoundingPolicy::Unset: +# if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + return factor; +# else // (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + return qRound(factor); +# endif // (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + case Qt::HighDpiScaleFactorRoundingPolicy::Round: + return qRound(factor); + case Qt::HighDpiScaleFactorRoundingPolicy::Ceil: + return qCeil(factor); + case Qt::HighDpiScaleFactorRoundingPolicy::Floor: + return qFloor(factor); + case Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor: + return (((factor - qreal(int(factor))) >= qreal(0.75)) ? qRound(factor) : qFloor(factor)); + case Qt::HighDpiScaleFactorRoundingPolicy::PassThrough: + return factor; + } + return 1; +#else // (QT_VERSION < QT_VERSION_CHECK(5, 14, 0)) + return qRound(factor); +#endif // (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) +} + FRAMELESSHELPER_END_NAMESPACE diff --git a/src/core/utils_win.cpp b/src/core/utils_win.cpp index 3e5f092..7ee93ed 100644 --- a/src/core/utils_win.cpp +++ b/src/core/utils_win.cpp @@ -1018,6 +1018,22 @@ static inline void moveWindowToMonitor(const HWND hwnd, const MONITORINFOEXW &ac } } +[[nodiscard]] static inline int getSystemMetrics2(const int index, const bool horizontal, + const quint32 dpi) +{ + Q_ASSERT(dpi != 0); + if (dpi == 0) { + return 0; + } + if (const int result = _GetSystemMetricsForDpi2(index, dpi); result > 0) { + return result; + } + static constexpr const auto defaultDpi = qreal(USER_DEFAULT_SCREEN_DPI); + const qreal currentDpr = (qreal(Utils::getPrimaryScreenDpi(horizontal)) / defaultDpi); + const qreal requestedDpr = (qreal(dpi) / defaultDpi); + return qRound(qreal(GetSystemMetrics(index)) / currentDpr * requestedDpr); +} + [[nodiscard]] static inline int getSystemMetrics2(const WId windowId, const int index, const bool horizontal, const bool scaled) { @@ -1720,6 +1736,21 @@ quint32 Utils::getWindowDpi(const WId windowId, const bool horizontal) return getPrimaryScreenDpi(horizontal); } +quint32 Utils::getResizeBorderThicknessForDpi(const bool horizontal, const quint32 dpi) +{ + Q_ASSERT(dpi != 0); + if (dpi == 0) { + return 0; + } + if (horizontal) { + return (getSystemMetrics2(SM_CXSIZEFRAME, true, dpi) + + getSystemMetrics2(SM_CXPADDEDBORDER, true, dpi)); + } else { + return (getSystemMetrics2(SM_CYSIZEFRAME, false, dpi) + + getSystemMetrics2(SM_CYPADDEDBORDER, false, dpi)); + } +} + quint32 Utils::getResizeBorderThickness(const WId windowId, const bool horizontal, const bool scaled) { Q_ASSERT(windowId); @@ -1735,6 +1766,15 @@ quint32 Utils::getResizeBorderThickness(const WId windowId, const bool horizonta } } +quint32 Utils::getCaptionBarHeightForDpi(const quint32 dpi) +{ + Q_ASSERT(dpi != 0); + if (dpi == 0) { + return 0; + } + return getSystemMetrics2(SM_CYCAPTION, false, dpi); +} + quint32 Utils::getCaptionBarHeight(const WId windowId, const bool scaled) { Q_ASSERT(windowId); @@ -1744,6 +1784,15 @@ quint32 Utils::getCaptionBarHeight(const WId windowId, const bool scaled) return getSystemMetrics2(windowId, SM_CYCAPTION, false, scaled); } +quint32 Utils::getTitleBarHeightForDpi(const quint32 dpi) +{ + Q_ASSERT(dpi != 0); + if (dpi == 0) { + return 0; + } + return (getCaptionBarHeightForDpi(dpi) + getResizeBorderThicknessForDpi(false, dpi)); +} + quint32 Utils::getTitleBarHeight(const WId windowId, const bool scaled) { Q_ASSERT(windowId); @@ -1753,6 +1802,20 @@ quint32 Utils::getTitleBarHeight(const WId windowId, const bool scaled) return (getCaptionBarHeight(windowId, scaled) + getResizeBorderThickness(windowId, false, scaled)); } +quint32 Utils::getFrameBorderThicknessForDpi(const quint32 dpi) +{ + Q_ASSERT(dpi != 0); + if (dpi == 0) { + return 0; + } + // There's no window frame border before Windows 10. + if (!WindowsVersionHelper::isWin10OrGreater()) { + return 0; + } + const qreal dpr = (qreal(dpi) / qreal(USER_DEFAULT_SCREEN_DPI)); + return qRound(qreal(kDefaultWindowFrameBorderThickness) * dpr); +} + quint32 Utils::getFrameBorderThickness(const WId windowId, const bool scaled) { Q_ASSERT(windowId);