Skip to content
This repository has been archived by the owner on Dec 19, 2023. It is now read-only.

Commit

Permalink
win: fix multi-monitor bug, take 3
Browse files Browse the repository at this point in the history
#141
#184

Signed-off-by: Yuhang Zhao <[email protected]>
  • Loading branch information
wangwenx190 committed Nov 24, 2022
1 parent ff5b171 commit a26df61
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 26 deletions.
18 changes: 9 additions & 9 deletions include/FramelessHelper/Core/framelesshelpercore_global.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions include/FramelessHelper/Core/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand Down
63 changes: 46 additions & 17 deletions src/core/framelesshelper_win.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ struct Win32HelperData
SystemParameters params = {};
bool trackingMouse = false;
WId fallbackTitleBarWindowId = 0;
Dpi dpi = {};
};

struct Win32Helper
Expand Down Expand Up @@ -521,14 +522,14 @@ void FramelessHelperWin::addWindow(const SystemParameters &params)
}
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
Expand Down Expand Up @@ -1123,28 +1124,56 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
const auto windowPos = reinterpret_cast<LPWINDOWPOS>(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<LPSIZE>(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<LPRECT>(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);
Expand Down
32 changes: 32 additions & 0 deletions src/core/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
63 changes: 63 additions & 0 deletions src/core/utils_win.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand Down

0 comments on commit a26df61

Please sign in to comment.