Commit a828bc2d authored by Hugo Beauzée-Luyssen's avatar Hugo Beauzée-Luyssen
Browse files

contribs: qt: Fix win32 window decorations on HiDPI screens

https://bugreports.qt.io/browse/QTBUG-53255
As a side note, Qt have been asked to include those patches included in Qt 5.6.x
branch, but it won't happen until 5.9 gets released, if ever.
parent 09ca4fc9
From 81ead1ff68711fed609cb47b33b3906c14ed95d5 Mon Sep 17 00:00:00 2001
From: Friedemann Kleint <Friedemann.Kleint@qt.io>
Date: Mon, 14 Nov 2016 16:08:51 +0100
Subject: [PATCH 1/2] Windows QPA: Reimplement calculation of window frames
Instead of relying on AdjustWindowRectEx() and dirty-handling,
capture the rectangles before and after the processing of
WM_NCCALCSIZE and calculate the frame from that. This allows
for changing window frames by handling WM_NCCALCSIZE and
monitor-dependent window frames when using High DPI scaling.
Task-number: QTBUG-53255
Task-number: QTBUG-40578
Task-number: QTBUG-56591
Change-Id: If8364a5440a6324ea5d470bf5b74e68942285abe
Reviewed-by: Tim Jenssen <tim.jenssen@qt.io>
Reviewed-by: Oliver Wolff <oliver.wolff@qt.io>
---
src/plugins/platforms/windows/qwindowscontext.cpp | 66 +++++++++++++++++++++--
src/plugins/platforms/windows/qwindowscontext.h | 4 +-
src/plugins/platforms/windows/qwindowswindow.cpp | 30 ++++-------
src/plugins/platforms/windows/qwindowswindow.h | 2 +-
4 files changed, 74 insertions(+), 28 deletions(-)
diff --git a/src/plugins/platforms/windows/qwindowscontext.cpp b/src/plugins/platforms/windows/qwindowscontext.cpp
index b611843..9058993 100644
--- a/src/plugins/platforms/windows/qwindowscontext.cpp
+++ b/src/plugins/platforms/windows/qwindowscontext.cpp
@@ -453,6 +453,11 @@ void QWindowsContext::setWindowCreationContext(const QSharedPointer<QWindowCreat
d->m_creationContext = ctx;
}
+QSharedPointer<QWindowCreationContext> QWindowsContext::windowCreationContext() const
+{
+ return d->m_creationContext;
+}
+
int QWindowsContext::defaultDPI() const
{
return d->m_defaultDPI;
@@ -916,7 +921,9 @@ static inline QWindowsInputContext *windowsInputContext()
bool QWindowsContext::windowsProc(HWND hwnd, UINT message,
QtWindows::WindowsEventType et,
- WPARAM wParam, LPARAM lParam, LRESULT *result)
+ WPARAM wParam, LPARAM lParam,
+ LRESULT *result,
+ QWindowsWindow **platformWindowPtr)
{
*result = 0;
@@ -949,6 +956,7 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message,
}
QWindowsWindow *platformWindow = findPlatformWindow(hwnd);
+ *platformWindowPtr = platformWindow;
if (platformWindow) {
filterResult = 0;
if (QWindowSystemInterface::handleNativeEvent(platformWindow->window(), d->m_eventType, &msg, &filterResult)) {
@@ -1144,9 +1152,6 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message,
return true;
case QtWindows::ThemeChanged: {
// Switch from Aero to Classic changes margins.
- const Qt::WindowFlags flags = platformWindow->window()->flags();
- if ((flags & Qt::WindowType_Mask) != Qt::Desktop && !(flags & Qt::FramelessWindowHint))
- platformWindow->setFlag(QWindowsWindow::FrameDirty);
if (QWindowsTheme *theme = QWindowsTheme::instance())
theme->windowsThemeChanged(platformWindow->window());
return true;
@@ -1318,6 +1323,37 @@ QTouchDevice *QWindowsContext::touchDevice() const
return d->m_mouseHandler.touchDevice();
}
+static inline bool isEmptyRect(const RECT &rect)
+{
+ return rect.right - rect.left == 0 && rect.bottom - rect.top == 0;
+}
+
+static inline QMargins marginsFromRects(const RECT &frame, const RECT &client)
+{
+ return QMargins(client.left - frame.left, client.top - frame.top,
+ frame.right - client.right, frame.bottom - client.bottom);
+}
+
+static RECT rectFromNcCalcSize(UINT message, WPARAM wParam, LPARAM lParam, int n)
+{
+ RECT result = {0, 0, 0, 0};
+ if (message == WM_NCCALCSIZE && wParam)
+ result = reinterpret_cast<const NCCALCSIZE_PARAMS *>(lParam)->rgrc[n];
+ return result;
+}
+
+static inline bool isMinimized(HWND hwnd)
+{
+ WINDOWPLACEMENT windowPlacement;
+ windowPlacement.length = sizeof(WINDOWPLACEMENT);
+ return GetWindowPlacement(hwnd, &windowPlacement) && windowPlacement.showCmd == SW_SHOWMINIMIZED;
+}
+
+static inline bool isTopLevel(HWND hwnd)
+{
+ return (GetWindowLongPtr(hwnd, GWL_STYLE) & WS_CHILD) == 0;
+}
+
/*!
\brief Windows functions for actual windows.
@@ -1331,7 +1367,9 @@ extern "C" LRESULT QT_WIN_CALLBACK qWindowsWndProc(HWND hwnd, UINT message, WPAR
{
LRESULT result;
const QtWindows::WindowsEventType et = windowsEventType(message, wParam, lParam);
- const bool handled = QWindowsContext::instance()->windowsProc(hwnd, message, et, wParam, lParam, &result);
+ QWindowsWindow *platformWindow = nullptr;
+ const RECT ncCalcSizeFrame = rectFromNcCalcSize(message, wParam, lParam, 0);
+ const bool handled = QWindowsContext::instance()->windowsProc(hwnd, message, et, wParam, lParam, &result, &platformWindow);
if (QWindowsContext::verbose > 1 && lcQpaEvents().isDebugEnabled()) {
if (const char *eventName = QWindowsGuiEventDispatcher::windowsMessageName(message)) {
qCDebug(lcQpaEvents) << "EVENT: hwd=" << hwnd << eventName << hex << "msg=0x" << message
@@ -1341,6 +1379,24 @@ extern "C" LRESULT QT_WIN_CALLBACK qWindowsWndProc(HWND hwnd, UINT message, WPAR
}
if (!handled)
result = DefWindowProc(hwnd, message, wParam, lParam);
+
+ // Capture WM_NCCALCSIZE on top level windows and obtain the window margins by
+ // subtracting the rectangles before and after processing. This will correctly
+ // capture client code overriding the message and allow for per-monitor margins
+ // for High DPI (QTBUG-53255, QTBUG-40578).
+ if (message == WM_NCCALCSIZE && !isEmptyRect(ncCalcSizeFrame) && isTopLevel(hwnd) && !isMinimized(hwnd)) {
+ const QMargins margins =
+ marginsFromRects(ncCalcSizeFrame, rectFromNcCalcSize(message, wParam, lParam, 0));
+ if (margins.left() >= 0) {
+ if (platformWindow) {
+ platformWindow->setFrameMargins(margins);
+ } else {
+ const QSharedPointer<QWindowCreationContext> ctx = QWindowsContext::instance()->windowCreationContext();
+ if (!ctx.isNull())
+ ctx->margins = margins;
+ }
+ }
+ }
return result;
}
diff --git a/src/plugins/platforms/windows/qwindowscontext.h b/src/plugins/platforms/windows/qwindowscontext.h
index 14baec9..dcac77c 100644
--- a/src/plugins/platforms/windows/qwindowscontext.h
+++ b/src/plugins/platforms/windows/qwindowscontext.h
@@ -210,12 +210,14 @@ public:
inline bool windowsProc(HWND hwnd, UINT message,
QtWindows::WindowsEventType et,
- WPARAM wParam, LPARAM lParam, LRESULT *result);
+ WPARAM wParam, LPARAM lParam, LRESULT *result,
+ QWindowsWindow **platformWindowPtr);
QWindow *keyGrabber() const;
void setKeyGrabber(QWindow *hwnd);
void setWindowCreationContext(const QSharedPointer<QWindowCreationContext> &ctx);
+ QSharedPointer<QWindowCreationContext> windowCreationContext() const;
void setTabletAbsoluteRange(int a);
void setProcessDpiAwareness(QtWindows::ProcessDpiAwareness dpiAwareness);
diff --git a/src/plugins/platforms/windows/qwindowswindow.cpp b/src/plugins/platforms/windows/qwindowswindow.cpp
index b38d7c2..11ba9c1 100644
--- a/src/plugins/platforms/windows/qwindowswindow.cpp
+++ b/src/plugins/platforms/windows/qwindowswindow.cpp
@@ -1630,7 +1630,6 @@ QWindowsWindowData QWindowsWindow::setWindowFlags_sys(Qt::WindowFlags wt,
QWindowsWindowData result = m_data;
result.flags = creationData.flags;
result.embedded = creationData.embedded;
- setFlag(FrameDirty);
return result;
}
@@ -1638,7 +1637,6 @@ void QWindowsWindow::handleWindowStateChange(Qt::WindowState state)
{
qCDebug(lcQpaWindows) << __FUNCTION__ << this << window()
<< "\n from " << m_windowState << " to " << state;
- setFlag(FrameDirty);
m_windowState = state;
QWindowSystemInterface::handleWindowStateChanged(window(), state);
switch (state) {
@@ -1715,8 +1713,6 @@ void QWindowsWindow::setWindowState_sys(Qt::WindowState newState)
const bool visible = isVisible();
- setFlag(FrameDirty);
-
if ((oldState == Qt::WindowFullScreen) != (newState == Qt::WindowFullScreen)) {
#ifdef Q_OS_WINCE
HWND handle = FindWindow(L"HHTaskBar", L"");
@@ -1826,7 +1822,6 @@ void QWindowsWindow::setStyle(unsigned s) const
{
qCDebug(lcQpaWindows) << __FUNCTION__ << this << window() << debugWinStyle(s);
setFlag(WithinSetStyle);
- setFlag(FrameDirty);
SetWindowLongPtr(m_data.hwnd, GWL_STYLE, s);
clearFlag(WithinSetStyle);
}
@@ -1835,7 +1830,6 @@ void QWindowsWindow::setExStyle(unsigned s) const
{
qCDebug(lcQpaWindows).nospace() << __FUNCTION__ << ' ' << this << ' ' << window()
<< " 0x" << QByteArray::number(s, 16);
- setFlag(FrameDirty);
SetWindowLongPtr(m_data.hwnd, GWL_EXSTYLE, s);
}
@@ -1909,22 +1903,17 @@ bool QWindowsWindow::handleGeometryChanging(MSG *message) const
return QWindowsWindow::handleGeometryChangingMessage(message, window(), margins);
}
-QMargins QWindowsWindow::frameMargins() const
+void QWindowsWindow::setFrameMargins(const QMargins &newMargins)
{
- // Frames are invalidated by style changes (window state, flags).
- // As they are also required for geometry calculations in resize
- // event sequences, introduce a dirty flag mechanism to be able
- // to cache results.
- if (testFlag(FrameDirty)) {
- // Always skip calculating style-dependent margins for windows claimed to be frameless.
- // This allows users to remove the margins by handling WM_NCCALCSIZE with WS_THICKFRAME set
- // to ensure Areo snap still works (QTBUG-40578).
- m_data.frame = m_data.flags & Qt::FramelessWindowHint
- ? QMargins(0, 0, 0, 0)
- : QWindowsGeometryHint::frame(style(), exStyle());
- clearFlag(FrameDirty);
+ if (m_data.frame != newMargins) {
+ qCDebug(lcQpaWindows) << __FUNCTION__ << window() << m_data.frame << "->" << newMargins;
+ m_data.frame = newMargins;
}
- return m_data.frame + m_data.customMargins;
+}
+
+QMargins QWindowsWindow::frameMargins() const
+{
+ return m_data.frame;
}
void QWindowsWindow::setOpacity(qreal level)
@@ -2322,7 +2311,6 @@ void QWindowsWindow::setCustomMargins(const QMargins &newCustomMargins)
const QPoint topLeft = currentFrameGeometry.topLeft();
QRect newFrame = currentFrameGeometry.marginsRemoved(oldCustomMargins) + m_data.customMargins;
newFrame.moveTo(topLeft);
- setFlag(FrameDirty);
qCDebug(lcQpaWindows) << __FUNCTION__ << oldCustomMargins << "->" << newCustomMargins
<< currentFrameGeometry << "->" << newFrame;
SetWindowPos(m_data.hwnd, 0, newFrame.x(), newFrame.y(), newFrame.width(), newFrame.height(), SWP_NOZORDER | SWP_FRAMECHANGED);
diff --git a/src/plugins/platforms/windows/qwindowswindow.h b/src/plugins/platforms/windows/qwindowswindow.h
index 6fffa1e..0e150a8 100644
--- a/src/plugins/platforms/windows/qwindowswindow.h
+++ b/src/plugins/platforms/windows/qwindowswindow.h
@@ -119,7 +119,6 @@ public:
{
AutoMouseCapture = 0x1, //! Automatic mouse capture on button press.
WithinSetParent = 0x2,
- FrameDirty = 0x4, //! Frame outdated by setStyle, recalculate in next query.
OpenGLSurface = 0x10,
OpenGL_ES2 = 0x20,
OpenGLDoubleBuffered = 0x40,
@@ -177,6 +176,7 @@ public:
static bool handleGeometryChangingMessage(MSG *message, const QWindow *qWindow, const QMargins &marginsDp);
bool handleGeometryChanging(MSG *message) const;
QMargins frameMargins() const Q_DECL_OVERRIDE;
+ void setFrameMargins(const QMargins &newMargins);
void setOpacity(qreal level) Q_DECL_OVERRIDE;
void setMask(const QRegion &region) Q_DECL_OVERRIDE;
--
2.9.0.windows.1
From 8f39b5247c4f7766553131688f5d5b7193327336 Mon Sep 17 00:00:00 2001
From: Friedemann Kleint <Friedemann.Kleint@qt.io>
Date: Mon, 7 Nov 2016 14:22:37 +0100
Subject: [PATCH 2/2] Windows QPA: Use new EnableNonClientDpiScaling() for
Windows decoration
Use newly introduced EnableNonClientDpiScaling() function to fix
the decoration having the wrong size in multimonitor setups with
per-monitor DPI awareness.
Task-number: QTBUG-53255
Change-Id: Ic6e2f2a92f790259107d2a0837b96177cf3adb5f
Reviewed-by: Tim Jenssen <tim.jenssen@qt.io>
---
src/plugins/platforms/windows/qtwindowsglobal.h | 3 ++
src/plugins/platforms/windows/qwindowscontext.cpp | 35 ++++++++++++++++++++++-
src/plugins/platforms/windows/qwindowscontext.h | 7 +++++
3 files changed, 44 insertions(+), 1 deletion(-)
diff --git a/src/plugins/platforms/windows/qtwindowsglobal.h b/src/plugins/platforms/windows/qtwindowsglobal.h
index 63c083d..e7e010d 100644
--- a/src/plugins/platforms/windows/qtwindowsglobal.h
+++ b/src/plugins/platforms/windows/qtwindowsglobal.h
@@ -87,6 +87,7 @@ enum WindowsEventType // Simplify event types
TouchEvent = TouchEventFlag + 1,
NonClientMouseEvent = NonClientEventFlag + MouseEventFlag + 1,
NonClientHitTest = NonClientEventFlag + 2,
+ NonClientCreate = NonClientEventFlag + 3,
KeyEvent = KeyEventFlag + 1,
KeyDownEvent = KeyEventFlag + KeyDownEventFlag + 1,
KeyboardLayoutChangeEvent = KeyEventFlag + 2,
@@ -164,6 +165,8 @@ inline QtWindows::WindowsEventType windowsEventType(UINT message, WPARAM wParamI
return QtWindows::HideEvent;
case WM_SIZE:
return QtWindows::ResizeEvent;
+ case WM_NCCREATE:
+ return QtWindows::NonClientCreate;
case WM_NCCALCSIZE:
return QtWindows::CalculateSize;
#ifndef Q_OS_WINCE
diff --git a/src/plugins/platforms/windows/qwindowscontext.cpp b/src/plugins/platforms/windows/qwindowscontext.cpp
index 9058993..ab0b3da 100644
--- a/src/plugins/platforms/windows/qwindowscontext.cpp
+++ b/src/plugins/platforms/windows/qwindowscontext.cpp
@@ -140,6 +140,28 @@ static inline QWindowsSessionManager *platformSessionManager() {
}
#endif
+static inline int windowDpiAwareness(HWND hwnd)
+{
+ return QWindowsContext::user32dll.getWindowDpiAwarenessContext && QWindowsContext::user32dll.getWindowDpiAwarenessContext
+ ? QWindowsContext::user32dll.getAwarenessFromDpiAwarenessContext(QWindowsContext::user32dll.getWindowDpiAwarenessContext(hwnd))
+ : -1;
+}
+
+// Note: This only works within WM_NCCREATE
+static bool enableNonClientDpiScaling(HWND hwnd)
+{
+ bool result = false;
+ if (QWindowsContext::user32dll.enableNonClientDpiScaling && windowDpiAwareness(hwnd) == 2) {
+ result = QWindowsContext::user32dll.enableNonClientDpiScaling(hwnd) != FALSE;
+ if (!result) {
+ const DWORD errorCode = GetLastError();
+ qErrnoWarning(int(errorCode), "EnableNonClientDpiScaling() failed for HWND %p (%lu)",
+ hwnd, errorCode);
+ }
+ }
+ return result;
+}
+
/*!
\class QWindowsUser32DLL
\brief Struct that contains dynamically resolved symbols of User32.dll.
@@ -165,7 +187,8 @@ QWindowsUser32DLL::QWindowsUser32DLL() :
registerTouchWindow(0), unregisterTouchWindow(0),
getTouchInputInfo(0), closeTouchInputHandle(0), setProcessDPIAware(0),
addClipboardFormatListener(0), removeClipboardFormatListener(0),
- getDisplayAutoRotationPreferences(0), setDisplayAutoRotationPreferences(0)
+ getDisplayAutoRotationPreferences(0), setDisplayAutoRotationPreferences(0),
+ enableNonClientDpiScaling(0), getWindowDpiAwarenessContext(0), getAwarenessFromDpiAwarenessContext(0)
{
}
@@ -188,6 +211,12 @@ void QWindowsUser32DLL::init()
}
getDisplayAutoRotationPreferences = (GetDisplayAutoRotationPreferences)library.resolve("GetDisplayAutoRotationPreferences");
setDisplayAutoRotationPreferences = (SetDisplayAutoRotationPreferences)library.resolve("SetDisplayAutoRotationPreferences");
+
+ if (QSysInfo::windowsVersion() >= QSysInfo::WV_WINDOWS10) { // Appears in 10.0.14393, October 2016
+ enableNonClientDpiScaling = (EnableNonClientDpiScaling)library.resolve("EnableNonClientDpiScaling");
+ getWindowDpiAwarenessContext = (GetWindowDpiAwarenessContext)library.resolve("GetWindowDpiAwarenessContext");
+ getAwarenessFromDpiAwarenessContext = (GetAwarenessFromDpiAwarenessContext)library.resolve("GetAwarenessFromDpiAwarenessContext");
+ }
}
bool QWindowsUser32DLL::initTouch()
@@ -1040,6 +1069,10 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message,
case QtWindows::MoveEvent:
d->m_creationContext->obtainedGeometry.moveTo(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
return true;
+ case QtWindows::NonClientCreate:
+ if (QSysInfo::windowsVersion() >= QSysInfo::WV_WINDOWS10 && d->m_creationContext->window->isTopLevel())
+ enableNonClientDpiScaling(msg.hwnd);
+ return false;
case QtWindows::CalculateSize:
return QWindowsGeometryHint::handleCalculateSize(d->m_creationContext->customMargins, msg, result);
case QtWindows::GeometryChangingEvent:
diff --git a/src/plugins/platforms/windows/qwindowscontext.h b/src/plugins/platforms/windows/qwindowscontext.h
index dcac77c..156ede5 100644
--- a/src/plugins/platforms/windows/qwindowscontext.h
+++ b/src/plugins/platforms/windows/qwindowscontext.h
@@ -96,6 +96,9 @@ struct QWindowsUser32DLL
typedef BOOL (WINAPI *RemoveClipboardFormatListener)(HWND);
typedef BOOL (WINAPI *GetDisplayAutoRotationPreferences)(DWORD *);
typedef BOOL (WINAPI *SetDisplayAutoRotationPreferences)(DWORD);
+ typedef BOOL (WINAPI *EnableNonClientDpiScaling)(HWND);
+ typedef int (WINAPI *GetWindowDpiAwarenessContext)(HWND);
+ typedef int (WINAPI *GetAwarenessFromDpiAwarenessContext)(int);
// Functions missing in Q_CC_GNU stub libraries.
SetLayeredWindowAttributes setLayeredWindowAttributes;
@@ -122,6 +125,10 @@ struct QWindowsUser32DLL
// Rotation API
GetDisplayAutoRotationPreferences getDisplayAutoRotationPreferences;
SetDisplayAutoRotationPreferences setDisplayAutoRotationPreferences;
+
+ EnableNonClientDpiScaling enableNonClientDpiScaling;
+ GetWindowDpiAwarenessContext getWindowDpiAwarenessContext;
+ GetAwarenessFromDpiAwarenessContext getAwarenessFromDpiAwarenessContext;
};
struct QWindowsShell32DLL
--
2.9.0.windows.1
......@@ -22,6 +22,8 @@ $(TARBALLS)/qt-$(QT_VERSION).tar.xz:
qt: qt-$(QT_VERSION).tar.xz .sum-qt
$(UNPACK)
mv qtbase-opensource-src-$(QT_VERSION) qt-$(QT_VERSION)
$(APPLY) $(SRC)/qt/0001-Windows-QPA-Reimplement-calculation-of-window-frames_56.patch
$(APPLY) $(SRC)/qt/0002-Windows-QPA-Use-new-EnableNonClientDpiScaling-for-Wi_56.patch
$(MOVE)
ifdef HAVE_MACOSX
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment