Improve WM_DPICHANGED handling

Resize QPlatformWindow on DPI change, so that QWindow
size can stay approximately constant.

For example, a 100x100 QWindow at 100% scaling will
have a 100x100 QPlatformWindow. If the scaling is changed
to 200% then the QPlatformWindow is resized to 200x200,
while the size of the QWindow stays at at 100x100.

In practice the QWindow size will also change slightly,
due to inaccuracies in how we adjust for the size of the
non-client window area. This will be addressed in a later commit.

We can get DPI change independently of screen change,
so no resizing should happen in screen change events.
Disable the resize code in QGuiApplication for Q_OS_WIN,
and remove the WithinDpiChanged flag.

The new flow for handling DPI change is:

  1) Send screen change (if any), so that the correct
     screen will be used when calculating scale factors
     during the following resize.
  2) Resize the native window, which will trigger geometry
     change events, possibly also for the QWindow.
  3) Resize child windows; WM_DPICHANGED is sent to
     top-level windows only.

Fixes: QTBUG-89294
Pick-to: 6.2
Change-Id: I0e2d44bae72d20ebdafc3d410db7be9964ad851b
Reviewed-by: Oliver Wolff <oliver.wolff@qt.io>
This commit is contained in:
Morten Johan Sørvig 2021-06-22 09:18:13 +02:00
parent f988386560
commit 6336b5350b
4 changed files with 31 additions and 53 deletions

View File

@ -2526,11 +2526,16 @@ void QGuiApplicationPrivate::processWindowScreenChangedEvent(QWindowSystemInterf
else // Fall back to default behavior, and try to find some appropriate screen
topLevelWindow->setScreen(nullptr);
}
// we may have changed scaling, so trigger resize event if needed
// We may have changed scaling; trigger resize event if needed,
// except on Windows, where we send resize events during WM_DPICHANGED
// event handling. FIXME: unify DPI change handling across all platforms.
#ifndef Q_OS_WIN
if (window->handle()) {
QWindowSystemInterfacePrivate::GeometryChangeEvent gce(window, QHighDpi::fromNativePixels(window->handle()->geometry(), window));
processGeometryChangeEvent(&gce);
}
#endif
}
}

View File

@ -1088,27 +1088,6 @@ static inline QWindowsInputContext *windowsInputContext()
return qobject_cast<QWindowsInputContext *>(QWindowsIntegration::instance()->inputContext());
}
// Child windows, fixed-size windows or pop-ups and similar should not be resized
static inline bool resizeOnDpiChanged(const QWindow *w)
{
bool result = false;
if (w->isTopLevel()) {
switch (w->type()) {
case Qt::Window:
case Qt::Dialog:
case Qt::Sheet:
case Qt::Drawer:
case Qt::Tool:
result = !w->flags().testFlag(Qt::MSWindowsFixedSizeDialogHint);
break;
default:
break;
}
}
return result;
}
bool QWindowsContext::shouldHaveNonClientDpiScaling(const QWindow *window)
{
// DPI aware V2 processes always have NonClientDpiScaling enabled.
@ -1477,26 +1456,30 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message,
case QtWindows::DpiChangedEvent: {
const UINT dpi = HIWORD(wParam);
const qreal scale = qreal(dpi) / qreal(platformWindow->savedDpi());
platformWindow->setSavedDpi(dpi);
// Try to apply the suggested size first and then notify ScreenChanged
// so that the resize event sent from QGuiApplication incorporates it
// WM_DPICHANGED is sent with a size that avoids resize loops (by
// snapping back to the previous screen, see QTBUG-65580).
const bool doResize = resizeOnDpiChanged(platformWindow->window());
if (doResize) {
platformWindow->setFlag(QWindowsWindow::WithinDpiChanged);
platformWindow->updateFullFrameMargins();
const auto prcNewWindow = reinterpret_cast<RECT *>(lParam);
qCDebug(lcQpaWindows) << __FUNCTION__ << "WM_DPICHANGED"
<< platformWindow->window() << *prcNewWindow;
SetWindowPos(hwnd, nullptr, prcNewWindow->left, prcNewWindow->top,
prcNewWindow->right - prcNewWindow->left,
prcNewWindow->bottom - prcNewWindow->top, SWP_NOZORDER | SWP_NOACTIVATE);
platformWindow->clearFlag(QWindowsWindow::WithinDpiChanged);
}
// Send screen change first, so that the new sceen is set during any following resize
platformWindow->checkForScreenChanged(QWindowsWindow::FromDpiChange);
return doResize;
// Apply the suggested window geometry to the native window. This will make
// sure the window tracks the mouse cursor during screen change, and also
// that the window size is scaled according to the DPI change.
platformWindow->updateFullFrameMargins();
const auto prcNewWindow = reinterpret_cast<RECT *>(lParam);
SetWindowPos(hwnd, nullptr, prcNewWindow->left, prcNewWindow->top,
prcNewWindow->right - prcNewWindow->left,
prcNewWindow->bottom - prcNewWindow->top, SWP_NOZORDER | SWP_NOACTIVATE);
// Scale child QPlatformWindow size. Windows sends WM_DPICHANGE to top-level windows only.
for (QWindow *childWindow : platformWindow->window()->findChildren<QWindow *>()) {
QWindowsWindow *platformChildWindow = static_cast<QWindowsWindow *>(childWindow->handle());
QRect currentGeometry = platformChildWindow->geometry();
QRect scaledGeometry = QRect(currentGeometry.topLeft() * scale, currentGeometry.size() * scale);
platformChildWindow->setGeometry(scaledGeometry);
}
return true;
}
#if QT_CONFIG(sessionmanager)
case QtWindows::QueryEndSessionApplicationEvent: {

View File

@ -1999,10 +1999,6 @@ void QWindowsWindow::handleGeometryChange()
{
const QRect previousGeometry = m_data.geometry;
m_data.geometry = geometry_sys();
if (testFlag(WithinDpiChanged)
&& QWindowsContext::instance()->screenManager().screenForHwnd(m_data.hwnd) != screen()) {
return; // QGuiApplication will send resize when screen actually changes
}
QWindowSystemInterface::handleGeometryChange(window(), m_data.geometry);
// QTBUG-32121: OpenGL/normal windows (with exception of ANGLE
// which we no longer support in Qt 6) do not receive expose
@ -2699,11 +2695,6 @@ static int getBorderWidth(const QPlatformScreen *screen)
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))
QWindowsGeometryHint::applyToMinMaxInfo(window(), fullFrameMargins(), mmi);
// This block fixes QTBUG-8361, QTBUG-4362: Frameless/title-less windows shouldn't cover the
// taskbar when maximized
if ((testFlag(WithinMaximize) || window()->windowStates().testFlag(Qt::WindowMinimized))

View File

@ -235,11 +235,10 @@ public:
MaximizeToFullScreen = 0x80000,
Compositing = 0x100000,
HasBorderInFullScreen = 0x200000,
WithinDpiChanged = 0x400000,
VulkanSurface = 0x800000,
ResizeMoveActive = 0x1000000,
DisableNonClientScaling = 0x2000000,
Direct3DSurface = 0x4000000
VulkanSurface = 0x400000,
ResizeMoveActive = 0x800000,
DisableNonClientScaling = 0x1000000,
Direct3DSurface = 0x2000000
};
QWindowsWindow(QWindow *window, const QWindowsWindowData &data);