From d2fd9b1b9818b3ec88487967e010f66e92952f55 Mon Sep 17 00:00:00 2001 From: Friedemann Kleint Date: Mon, 6 May 2019 15:06:52 +0200 Subject: Windows QPA: Fix window frame calculation in multi-monitor setups When introducing EnableNonClientDpiScaling() for QTBUG-53255, the window frame calculation was not adapted. That is, window frames were calculated from the style for the primary screen only, causing - minimum size constraints not being calculated correctly for applications on secondary screens when populating the MINMAXINFO structure. - warnings about not being able to apply a geometry when moving fixed size windows across screens. The calculation of the frames for propagating size hints is also no longer required after 3035400f36731c400adb9204b94e9afe346a71b7, which retrieves them from the WM_NCCALCSIZE message; QWindowsWindow::fullFrameMargins() can be used instead. For newly created windows, use the newly added AdjustWindowRectExForDpi() function to calculate the initial frame size. Change QWindowsGeometryHint from a class to a collection of static functions and add overloads to calculate the frame. In checkForScreenChanged(), update the margins until WM_NCCALCSIZE is received. Task-number: QTBUG-67777 Task-number: QTBUG-65580 Task-number: QTBUG-53255 Change-Id: Iff2d382b2b316adec6c1a0622ae8015dba6de371 Reviewed-by: Oliver Wolff Reviewed-by: Andre de la Rocha --- src/plugins/platforms/windows/qwindowscontext.cpp | 4 +- src/plugins/platforms/windows/qwindowscontext.h | 4 + .../platforms/windows/qwindowsintegration.cpp | 3 + src/plugins/platforms/windows/qwindowsscreen.cpp | 6 + src/plugins/platforms/windows/qwindowsscreen.h | 2 + src/plugins/platforms/windows/qwindowswindow.cpp | 156 +++++++++++++++------ src/plugins/platforms/windows/qwindowswindow.h | 31 ++-- 7 files changed, 146 insertions(+), 60 deletions(-) (limited to 'src/plugins/platforms/windows') diff --git a/src/plugins/platforms/windows/qwindowscontext.cpp b/src/plugins/platforms/windows/qwindowscontext.cpp index 6c1f5c8f93..8d1ef9f34a 100644 --- a/src/plugins/platforms/windows/qwindowscontext.cpp +++ b/src/plugins/platforms/windows/qwindowscontext.cpp @@ -210,6 +210,7 @@ void QWindowsUser32DLL::init() if (QOperatingSystemVersion::current() >= QOperatingSystemVersion(QOperatingSystemVersion::Windows, 10, 0, 14393)) { + adjustWindowRectExForDpi = (AdjustWindowRectExForDpi)library.resolve("AdjustWindowRectExForDpi"); enableNonClientDpiScaling = (EnableNonClientDpiScaling)library.resolve("EnableNonClientDpiScaling"); getWindowDpiAwarenessContext = (GetWindowDpiAwarenessContext)library.resolve("GetWindowDpiAwarenessContext"); getAwarenessFromDpiAwarenessContext = (GetAwarenessFromDpiAwarenessContext)library.resolve("GetAwarenessFromDpiAwarenessContext"); @@ -977,7 +978,7 @@ static inline bool resizeOnDpiChanged(const QWindow *w) return result; } -static bool shouldHaveNonClientDpiScaling(const QWindow *window) +bool QWindowsContext::shouldHaveNonClientDpiScaling(const QWindow *window) { return QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows10 && window->isTopLevel() @@ -1589,6 +1590,7 @@ extern "C" LRESULT QT_WIN_CALLBACK qWindowsWndProc(HWND hwnd, UINT message, WPAR marginsFromRects(ncCalcSizeFrame, rectFromNcCalcSize(message, wParam, lParam, 0)); if (margins.left() >= 0) { if (platformWindow) { + qCDebug(lcQpaWindows) << __FUNCTION__ << "WM_NCCALCSIZE for" << hwnd << margins; platformWindow->setFullFrameMargins(margins); } else { const QSharedPointer ctx = QWindowsContext::instance()->windowCreationContext(); diff --git a/src/plugins/platforms/windows/qwindowscontext.h b/src/plugins/platforms/windows/qwindowscontext.h index fd6c72668c..4908f14629 100644 --- a/src/plugins/platforms/windows/qwindowscontext.h +++ b/src/plugins/platforms/windows/qwindowscontext.h @@ -102,6 +102,7 @@ struct QWindowsUser32DLL typedef BOOL (WINAPI *RemoveClipboardFormatListener)(HWND); typedef BOOL (WINAPI *GetDisplayAutoRotationPreferences)(DWORD *); typedef BOOL (WINAPI *SetDisplayAutoRotationPreferences)(DWORD); + typedef BOOL (WINAPI *AdjustWindowRectExForDpi)(LPRECT,DWORD,BOOL,DWORD,UINT); typedef BOOL (WINAPI *EnableNonClientDpiScaling)(HWND); typedef int (WINAPI *GetWindowDpiAwarenessContext)(HWND); typedef int (WINAPI *GetAwarenessFromDpiAwarenessContext)(int); @@ -131,6 +132,7 @@ struct QWindowsUser32DLL GetDisplayAutoRotationPreferences getDisplayAutoRotationPreferences = nullptr; SetDisplayAutoRotationPreferences setDisplayAutoRotationPreferences = nullptr; + AdjustWindowRectExForDpi adjustWindowRectExForDpi = nullptr; EnableNonClientDpiScaling enableNonClientDpiScaling = nullptr; GetWindowDpiAwarenessContext getWindowDpiAwarenessContext = nullptr; GetAwarenessFromDpiAwarenessContext getAwarenessFromDpiAwarenessContext = nullptr; @@ -201,6 +203,8 @@ public: QWindowsWindow *findPlatformWindowAt(HWND parent, const QPoint &screenPoint, unsigned cwex_flags) const; + static bool shouldHaveNonClientDpiScaling(const QWindow *window); + QWindow *windowUnderMouse() const; void clearWindowUnderMouse(); diff --git a/src/plugins/platforms/windows/qwindowsintegration.cpp b/src/plugins/platforms/windows/qwindowsintegration.cpp index 2c90b0484e..5c1fa00088 100644 --- a/src/plugins/platforms/windows/qwindowsintegration.cpp +++ b/src/plugins/platforms/windows/qwindowsintegration.cpp @@ -353,6 +353,9 @@ QPlatformWindow *QWindowsIntegration::createPlatformWindow(QWindow *window) cons QWindowsWindow *result = createPlatformWindowHelper(window, obtained); Q_ASSERT(result); + if (window->isTopLevel() && !QWindowsContext::shouldHaveNonClientDpiScaling(window)) + result->setFlag(QWindowsWindow::DisableNonClientScaling); + if (QWindowsMenuBar *menuBarToBeInstalled = QWindowsMenuBar::menuBarOf(window)) menuBarToBeInstalled->install(result); diff --git a/src/plugins/platforms/windows/qwindowsscreen.cpp b/src/plugins/platforms/windows/qwindowsscreen.cpp index 94608bfd82..b28a113ce6 100644 --- a/src/plugins/platforms/windows/qwindowsscreen.cpp +++ b/src/plugins/platforms/windows/qwindowsscreen.cpp @@ -435,6 +435,12 @@ QPlatformScreen::SubpixelAntialiasingType QWindowsScreen::subpixelAntialiasingTy QWindowsScreenManager::QWindowsScreenManager() = default; + +bool QWindowsScreenManager::isSingleScreen() +{ + return QWindowsContext::instance()->screenManager().screens().size() < 2; +} + /*! \brief Triggers synchronization of screens (WM_DISPLAYCHANGE). diff --git a/src/plugins/platforms/windows/qwindowsscreen.h b/src/plugins/platforms/windows/qwindowsscreen.h index 824bcb1ad6..8ad012512e 100644 --- a/src/plugins/platforms/windows/qwindowsscreen.h +++ b/src/plugins/platforms/windows/qwindowsscreen.h @@ -138,6 +138,8 @@ public: const QWindowsScreen *screenAtDp(const QPoint &p) const; const QWindowsScreen *screenForHwnd(HWND hwnd) const; + static bool isSingleScreen(); + private: void removeScreen(int index); diff --git a/src/plugins/platforms/windows/qwindowswindow.cpp b/src/plugins/platforms/windows/qwindowswindow.cpp index f538b6bad7..841d3dccdc 100644 --- a/src/plugins/platforms/windows/qwindowswindow.cpp +++ b/src/plugins/platforms/windows/qwindowswindow.cpp @@ -870,35 +870,78 @@ static QSize toNativeSizeConstrained(QSize dip, const QWindow *w) \ingroup qt-lighthouse-win */ -QWindowsGeometryHint::QWindowsGeometryHint(const QWindow *w, const QMargins &cm) : - minimumSize(toNativeSizeConstrained(w->minimumSize(), w)), - maximumSize(toNativeSizeConstrained(w->maximumSize(), w)), - customMargins(cm) +QMargins QWindowsGeometryHint::frameOnPrimaryScreen(DWORD style, DWORD exStyle) { + RECT rect = {0,0,0,0}; + style &= ~DWORD(WS_OVERLAPPED); // Not permitted, see docs. + if (AdjustWindowRectEx(&rect, style, FALSE, exStyle) == FALSE) + qErrnoWarning("%s: AdjustWindowRectEx failed", __FUNCTION__); + const QMargins result(qAbs(rect.left), qAbs(rect.top), + qAbs(rect.right), qAbs(rect.bottom)); + qCDebug(lcQpaWindows).nospace() << __FUNCTION__ << " style=" + << showbase << hex << style << " exStyle=" << exStyle << dec << noshowbase + << ' ' << rect << ' ' << result; + return result; } -bool QWindowsGeometryHint::validSize(const QSize &s) const +QMargins QWindowsGeometryHint::frameOnPrimaryScreen(HWND hwnd) { - const int width = s.width(); - const int height = s.height(); - return width >= minimumSize.width() && width <= maximumSize.width() - && height >= minimumSize.height() && height <= maximumSize.height(); + return frameOnPrimaryScreen(DWORD(GetWindowLongPtr(hwnd, GWL_STYLE)), + DWORD(GetWindowLongPtr(hwnd, GWL_EXSTYLE))); } -QMargins QWindowsGeometryHint::frame(DWORD style, DWORD exStyle) +QMargins QWindowsGeometryHint::frame(DWORD style, DWORD exStyle, qreal dpi) { + if (QWindowsContext::user32dll.adjustWindowRectExForDpi == nullptr) + return frameOnPrimaryScreen(style, exStyle); RECT rect = {0,0,0,0}; - style &= ~(WS_OVERLAPPED); // Not permitted, see docs. - if (!AdjustWindowRectEx(&rect, style, FALSE, exStyle)) - qErrnoWarning("%s: AdjustWindowRectEx failed", __FUNCTION__); + style &= ~DWORD(WS_OVERLAPPED); // Not permitted, see docs. + if (QWindowsContext::user32dll.adjustWindowRectExForDpi(&rect, style, FALSE, exStyle, + unsigned(qRound(dpi))) == FALSE) { + qErrnoWarning("%s: AdjustWindowRectExForDpi failed", __FUNCTION__); + } const QMargins result(qAbs(rect.left), qAbs(rect.top), qAbs(rect.right), qAbs(rect.bottom)); qCDebug(lcQpaWindows).nospace() << __FUNCTION__ << " style=" << showbase << hex << style << " exStyle=" << exStyle << dec << noshowbase + << " dpi=" << dpi << ' ' << rect << ' ' << result; return result; } +QMargins QWindowsGeometryHint::frame(HWND hwnd, DWORD style, DWORD exStyle) +{ + if (QWindowsScreenManager::isSingleScreen()) + return frameOnPrimaryScreen(style, exStyle); + auto screenManager = QWindowsContext::instance()->screenManager(); + auto screen = screenManager.screenForHwnd(hwnd); + if (!screen) + screen = screenManager.screens().value(0); + const auto dpi = screen ? screen->logicalDpi().first : qreal(96); + return frame(style, exStyle, dpi); +} + +// For newly created windows. +QMargins QWindowsGeometryHint::frame(const QWindow *w, const QRect &geometry, + DWORD style, DWORD exStyle) +{ + if (!w->isTopLevel() || w->flags().testFlag(Qt::FramelessWindowHint)) + return {}; + if (!QWindowsContext::user32dll.adjustWindowRectExForDpi + || QWindowsScreenManager::isSingleScreen() + || !QWindowsContext::shouldHaveNonClientDpiScaling(w)) { + return frameOnPrimaryScreen(style, exStyle); + } + qreal dpi = 96; + auto screenManager = QWindowsContext::instance()->screenManager(); + auto screen = screenManager.screenAtDp(geometry.center()); + if (!screen) + screen = screenManager.screens().value(0); + if (screen) + dpi = screen->logicalDpi().first; + return QWindowsGeometryHint::frame(style, exStyle, dpi); +} + bool QWindowsGeometryHint::handleCalculateSize(const QMargins &customMargins, const MSG &msg, LRESULT *result) { // NCCALCSIZE_PARAMS structure if wParam==TRUE @@ -918,36 +961,50 @@ bool QWindowsGeometryHint::handleCalculateSize(const QMargins &customMargins, co return true; } -void QWindowsGeometryHint::applyToMinMaxInfo(HWND hwnd, MINMAXINFO *mmi) const +void QWindowsGeometryHint::frameSizeConstraints(const QWindow *w, const QMargins &margins, + QSize *minimumSize, QSize *maximumSize) { - return applyToMinMaxInfo(DWORD(GetWindowLong(hwnd, GWL_STYLE)), - DWORD(GetWindowLong(hwnd, GWL_EXSTYLE)), mmi); + *minimumSize = toNativeSizeConstrained(w->minimumSize(), w); + *maximumSize = toNativeSizeConstrained(w->maximumSize(), w); + + const int maximumWidth = qMax(maximumSize->width(), minimumSize->width()); + const int maximumHeight = qMax(maximumSize->height(), minimumSize->height()); + const int frameWidth = margins.left() + margins.right(); + const int frameHeight = margins.top() + margins.bottom(); + + if (minimumSize->width() > 0) + minimumSize->rwidth() += frameWidth; + if (minimumSize->height() > 0) + minimumSize->rheight() += frameHeight; + if (maximumWidth < QWINDOWSIZE_MAX) + maximumSize->setWidth(maximumWidth + frameWidth); + if (maximumHeight < QWINDOWSIZE_MAX) + maximumSize->setHeight(maximumHeight + frameHeight); } -void QWindowsGeometryHint::applyToMinMaxInfo(DWORD style, DWORD exStyle, MINMAXINFO *mmi) const +void QWindowsGeometryHint::applyToMinMaxInfo(const QWindow *w, + const QMargins &margins, + MINMAXINFO *mmi) { + QSize minimumSize; + QSize maximumSize; + frameSizeConstraints(w, margins, &minimumSize, &maximumSize); qCDebug(lcQpaWindows).nospace() << '>' << __FUNCTION__ << '<' << " min=" << minimumSize.width() << ',' << minimumSize.height() << " max=" << maximumSize.width() << ',' << maximumSize.height() + << " margins=" << margins << " in " << *mmi; - const QMargins margins = QWindowsGeometryHint::frame(style, exStyle); - const int frameWidth = margins.left() + margins.right() + customMargins.left() + customMargins.right(); - const int frameHeight = margins.top() + margins.bottom() + customMargins.top() + customMargins.bottom(); if (minimumSize.width() > 0) - mmi->ptMinTrackSize.x = minimumSize.width() + frameWidth; + mmi->ptMinTrackSize.x = minimumSize.width(); if (minimumSize.height() > 0) - mmi->ptMinTrackSize.y = minimumSize.height() + frameHeight; + mmi->ptMinTrackSize.y = minimumSize.height(); - const int maximumWidth = qMax(maximumSize.width(), minimumSize.width()); - const int maximumHeight = qMax(maximumSize.height(), minimumSize.height()); - if (maximumWidth < QWINDOWSIZE_MAX) - mmi->ptMaxTrackSize.x = maximumWidth + frameWidth; - if (maximumHeight < QWINDOWSIZE_MAX) - mmi->ptMaxTrackSize.y = maximumHeight + frameHeight; - qCDebug(lcQpaWindows).nospace() << '<' << __FUNCTION__ - << " frame=" << margins << ' ' << frameWidth << ',' << frameHeight - << " out " << *mmi; + if (maximumSize.width() < QWINDOWSIZE_MAX) + mmi->ptMaxTrackSize.x = maximumSize.width(); + if (maximumSize.height() < QWINDOWSIZE_MAX) + mmi->ptMaxTrackSize.y = maximumSize.height(); + qCDebug(lcQpaWindows).nospace() << '<' << __FUNCTION__ << " out " << *mmi; } bool QWindowsGeometryHint::positionIncludesFrame(const QWindow *w) @@ -1007,7 +1064,7 @@ QRect QWindowsBaseWindow::geometry_sys() const QMargins QWindowsBaseWindow::frameMargins_sys() const { - return QWindowsGeometryHint::frame(style(), exStyle()); + return QWindowsGeometryHint::frame(handle(), style(), exStyle()); } void QWindowsBaseWindow::hide_sys() // Normal hide, do not activate other windows. @@ -1133,11 +1190,12 @@ void QWindowsForeignWindow::setVisible(bool visible) QWindowCreationContext::QWindowCreationContext(const QWindow *w, const QRect &geometryIn, const QRect &geometry, const QMargins &cm, - DWORD style_, DWORD exStyle_) : - geometryHint(w, cm), window(w), style(style_), exStyle(exStyle_), + DWORD style, DWORD exStyle) : + window(w), requestedGeometryIn(geometryIn), requestedGeometry(geometry), obtainedGeometry(geometry), - margins(QWindowsGeometryHint::frame(style, exStyle)), customMargins(cm) + margins(QWindowsGeometryHint::frame(w, geometry, style, exStyle)), + customMargins(cm) { // Geometry of toplevels does not consider window frames. // TODO: No concept of WA_wasMoved yet that would indicate a @@ -1166,8 +1224,12 @@ QWindowCreationContext::QWindowCreationContext(const QWindow *w, << " pos incl. frame=" << QWindowsGeometryHint::positionIncludesFrame(w) << " frame=" << frameWidth << 'x' << frameHeight << '+' << frameX << '+' << frameY - << " min=" << geometryHint.minimumSize << " max=" << geometryHint.maximumSize - << " custom margins=" << customMargins; + << " margins=" << margins << " custom margins=" << customMargins; +} + +void QWindowCreationContext::applyToMinMaxInfo(MINMAXINFO *mmi) const +{ + QWindowsGeometryHint::applyToMinMaxInfo(window, margins + customMargins, mmi); } /*! @@ -1682,7 +1744,9 @@ QRect QWindowsWindow::normalGeometry() const const bool fakeFullScreen = m_savedFrameGeometry.isValid() && (window()->windowStates() & Qt::WindowFullScreen); const QRect frame = fakeFullScreen ? m_savedFrameGeometry : normalFrameGeometry(m_data.hwnd); - const QMargins margins = fakeFullScreen ? QWindowsGeometryHint::frame(m_savedStyle, 0) : fullFrameMargins(); + const QMargins margins = fakeFullScreen + ? QWindowsGeometryHint::frame(handle(), m_savedStyle, 0) + : fullFrameMargins(); return frame.isValid() ? frame.marginsRemoved(margins) : frame; } @@ -1809,6 +1873,7 @@ void QWindowsWindow::checkForScreenChanged() qCDebug(lcQpaWindows).noquote().nospace() << __FUNCTION__ << ' ' << window() << " \"" << currentScreen->name() << "\"->\"" << newScreen->name() << '"'; + updateFullFrameMargins(); setFlag(SynchronousGeometryChangeEvent); QWindowSystemInterface::handleWindowScreenChanged(window(), newScreen->screen()); } @@ -2280,6 +2345,15 @@ void QWindowsWindow::setFullFrameMargins(const QMargins &newMargins) } } +void QWindowsWindow::updateFullFrameMargins() +{ + // Normally obtained from WM_NCCALCSIZE + const auto systemMargins = testFlag(DisableNonClientScaling) + ? QWindowsGeometryHint::frameOnPrimaryScreen(m_data.hwnd) + : frameMargins_sys(); + setFullFrameMargins(systemMargins + m_data.customMargins); +} + QMargins QWindowsWindow::frameMargins() const { QMargins result = fullFrameMargins(); @@ -2490,10 +2564,8 @@ void QWindowsWindow::getSizeHints(MINMAXINFO *mmi) const { // We don't apply the min/max size hint as we change the dpi, because we did not adjust the // QScreen of the window yet so we don't have the min/max with the right ratio - if (!testFlag(QWindowsWindow::WithinDpiChanged)) { - const QWindowsGeometryHint hint(window(), m_data.customMargins); - hint.applyToMinMaxInfo(m_data.hwnd, mmi); - } + if (!testFlag(QWindowsWindow::WithinDpiChanged)) + QWindowsGeometryHint::applyToMinMaxInfo(window(), fullFrameMargins(), mmi); // This block fixes QTBUG-8361, QTBUG-4362: Frameless/title-less windows shouldn't cover the // taskbar when maximized diff --git a/src/plugins/platforms/windows/qwindowswindow.h b/src/plugins/platforms/windows/qwindowswindow.h index 2675990cf1..5e44511e5d 100644 --- a/src/plugins/platforms/windows/qwindowswindow.h +++ b/src/plugins/platforms/windows/qwindowswindow.h @@ -59,24 +59,23 @@ class QDebug; struct QWindowsGeometryHint { - QWindowsGeometryHint() = default; - explicit QWindowsGeometryHint(const QWindow *w, const QMargins &customMargins); - static QMargins frame(DWORD style, DWORD exStyle); + static QMargins frameOnPrimaryScreen(DWORD style, DWORD exStyle); + static QMargins frameOnPrimaryScreen(HWND hwnd); + static QMargins frame(DWORD style, DWORD exStyle, qreal dpi); + static QMargins frame(HWND hwnd, DWORD style, DWORD exStyle); + static QMargins frame(const QWindow *w, const QRect &geometry, + DWORD style, DWORD exStyle); static bool handleCalculateSize(const QMargins &customMargins, const MSG &msg, LRESULT *result); - void applyToMinMaxInfo(DWORD style, DWORD exStyle, MINMAXINFO *mmi) const; - void applyToMinMaxInfo(HWND hwnd, MINMAXINFO *mmi) const; - bool validSize(const QSize &s) const; - + static void applyToMinMaxInfo(const QWindow *w, const QMargins &margins, + MINMAXINFO *mmi); + static void frameSizeConstraints(const QWindow *w, const QMargins &margins, + QSize *minimumSize, QSize *maximumSize); static inline QPoint mapToGlobal(HWND hwnd, const QPoint &); static inline QPoint mapToGlobal(const QWindow *w, const QPoint &); static inline QPoint mapFromGlobal(const HWND hwnd, const QPoint &); static inline QPoint mapFromGlobal(const QWindow *w, const QPoint &); static bool positionIncludesFrame(const QWindow *w); - - QSize minimumSize; - QSize maximumSize; - QMargins customMargins; }; struct QWindowCreationContext @@ -85,13 +84,9 @@ struct QWindowCreationContext const QRect &geometryIn, const QRect &geometry, const QMargins &customMargins, DWORD style, DWORD exStyle); - void applyToMinMaxInfo(MINMAXINFO *mmi) const - { geometryHint.applyToMinMaxInfo(style, exStyle, mmi); } + void applyToMinMaxInfo(MINMAXINFO *mmi) const; - QWindowsGeometryHint geometryHint; const QWindow *window; - DWORD style; - DWORD exStyle; QRect requestedGeometryIn; // QWindow scaled QRect requestedGeometry; // after QPlatformWindow::initialGeometry() QRect obtainedGeometry; @@ -221,7 +216,8 @@ public: HasBorderInFullScreen = 0x200000, WithinDpiChanged = 0x400000, VulkanSurface = 0x800000, - ResizeMoveActive = 0x1000000 + ResizeMoveActive = 0x1000000, + DisableNonClientScaling = 0x2000000 }; QWindowsWindow(QWindow *window, const QWindowsWindowData &data); @@ -262,6 +258,7 @@ public: QMargins frameMargins() const override; QMargins fullFrameMargins() const override; void setFullFrameMargins(const QMargins &newMargins); + void updateFullFrameMargins(); void setOpacity(qreal level) override; void setMask(const QRegion ®ion) override; -- cgit v1.2.3