Win32: Add workaround for DWM flicker

And some other minor tweaks.

Signed-off-by: Yuhang Zhao <2546789017@qq.com>
This commit is contained in:
Yuhang Zhao 2021-12-01 14:59:29 +08:00
parent 6068944657
commit fb8f061091
6 changed files with 81 additions and 23 deletions

View File

@ -73,7 +73,7 @@ target_compile_definitions(${PROJECT_NAME} PRIVATE
QT_NO_CAST_TO_ASCII QT_NO_CAST_TO_ASCII
QT_NO_KEYWORDS QT_NO_KEYWORDS
QT_DEPRECATED_WARNINGS QT_DEPRECATED_WARNINGS
QT_DISABLE_DEPRECATED_BEFORE=0x060100 QT_DISABLE_DEPRECATED_BEFORE=0x060200
FRAMELESSHELPER_BUILD_LIBRARY FRAMELESSHELPER_BUILD_LIBRARY
) )
@ -85,7 +85,7 @@ endif()
if(WIN32) if(WIN32)
target_link_libraries(${PROJECT_NAME} PRIVATE target_link_libraries(${PROJECT_NAME} PRIVATE
dwmapi dwmapi winmm
) )
endif() endif()

View File

@ -5,12 +5,12 @@ DEFINES += \
QT_NO_CAST_TO_ASCII \ QT_NO_CAST_TO_ASCII \
QT_NO_KEYWORDS \ QT_NO_KEYWORDS \
QT_DEPRECATED_WARNINGS \ QT_DEPRECATED_WARNINGS \
QT_DISABLE_DEPRECATED_BEFORE=0x060100 QT_DISABLE_DEPRECATED_BEFORE=0x060200
RESOURCES += $$PWD/images.qrc RESOURCES += $$PWD/images.qrc
win32 { win32 {
CONFIG += windeployqt CONFIG += windeployqt
CONFIG -= embed_manifest_exe CONFIG -= embed_manifest_exe
LIBS += -luser32 -lshell32 -ldwmapi LIBS += -luser32 -lshell32 -ldwmapi -lwinmm
RC_FILE = $$PWD/example.rc RC_FILE = $$PWD/example.rc
OTHER_FILES += $$PWD/example.manifest OTHER_FILES += $$PWD/example.manifest
} }

View File

@ -1,5 +1,3 @@
/* /*
* MIT License * MIT License
* *
@ -23,6 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE. * SOFTWARE.
*/ */
import QtQuick 2.0 import QtQuick 2.0
import QtQuick.Window 2.0 import QtQuick.Window 2.0
import QtQuick.Controls 2.0 import QtQuick.Controls 2.0
@ -48,13 +47,13 @@ Window {
interval: 500 interval: 500
running: true running: true
repeat: true repeat: true
onTriggered: label.text = Qt.formatTime(new Date(), "hh:mm:ss") onTriggered: timeLabel.text = Qt.formatTime(new Date(), "hh:mm:ss")
} }
Rectangle { Rectangle {
id: titleBar id: titleBar
height: framelessHelper.titleBarHeight height: framelessHelper.titleBarHeight
color: "transparent" color: "white"
anchors { anchors {
top: parent.top top: parent.top
topMargin: window._flh_margin topMargin: window._flh_margin
@ -106,7 +105,7 @@ Window {
} }
Label { Label {
id: label id: timeLabel
anchors.centerIn: parent anchors.centerIn: parent
font { font {
pointSize: 70 pointSize: 70
@ -115,9 +114,10 @@ Window {
} }
Button { Button {
id: fullScreenButton
anchors { anchors {
horizontalCenter: parent.horizontalCenter horizontalCenter: parent.horizontalCenter
top: label.bottom top: timeLabel.bottom
topMargin: 15 topMargin: 15
} }
property bool _full: window.visibility === Window.FullScreen property bool _full: window.visibility === Window.FullScreen

View File

@ -245,10 +245,12 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
const auto clientRect = ((static_cast<BOOL>(msg->wParam) == FALSE) const auto clientRect = ((static_cast<BOOL>(msg->wParam) == FALSE)
? reinterpret_cast<LPRECT>(msg->lParam) ? reinterpret_cast<LPRECT>(msg->lParam)
: &(reinterpret_cast<LPNCCALCSIZE_PARAMS>(msg->lParam))->rgrc[0]); : &(reinterpret_cast<LPNCCALCSIZE_PARAMS>(msg->lParam))->rgrc[0]);
const bool max = IsMaximized(msg->hwnd);
const bool full = window->windowState() == Qt::WindowFullScreen;
// We don't need this correction when we're fullscreen. We will // We don't need this correction when we're fullscreen. We will
// have the WS_POPUP size, so we don't have to worry about // have the WS_POPUP size, so we don't have to worry about
// borders, and the default frame will be fine. // borders, and the default frame will be fine.
if (IsMaximized(msg->hwnd) && (window->windowState() != Qt::WindowFullScreen)) { if (max && !full) {
// When a window is maximized, its size is actually a little bit more // When a window is maximized, its size is actually a little bit more
// than the monitor's work area. The window is positioned and sized in // than the monitor's work area. The window is positioned and sized in
// such a way that the resize handles are outside of the monitor and // such a way that the resize handles are outside of the monitor and
@ -267,7 +269,7 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
// Make sure to use MONITOR_DEFAULTTONEAREST, so that this will // Make sure to use MONITOR_DEFAULTTONEAREST, so that this will
// still find the right monitor even when we're restoring from // still find the right monitor even when we're restoring from
// minimized. // minimized.
if (IsMaximized(msg->hwnd) || (window->windowState() == Qt::WindowFullScreen)) { if (max || full) {
APPBARDATA abd; APPBARDATA abd;
SecureZeroMemory(&abd, sizeof(abd)); SecureZeroMemory(&abd, sizeof(abd));
abd.cbSize = sizeof(abd); abd.cbSize = sizeof(abd);
@ -308,8 +310,6 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
left = hasAutohideTaskbar(ABE_LEFT); left = hasAutohideTaskbar(ABE_LEFT);
right = hasAutohideTaskbar(ABE_RIGHT); right = hasAutohideTaskbar(ABE_RIGHT);
} else { } else {
// The following code is copied from Mozilla Firefox,
// with some modifications.
int edge = -1; int edge = -1;
APPBARDATA _abd; APPBARDATA _abd;
SecureZeroMemory(&_abd, sizeof(_abd)); SecureZeroMemory(&_abd, sizeof(_abd));
@ -361,7 +361,7 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
} }
} }
} }
#if 1 #if 0
// Fix the flickering issue while resizing. // Fix the flickering issue while resizing.
// "clientRect->right += 1;" also works. // "clientRect->right += 1;" also works.
// This small technique is known to have two draw backs: // This small technique is known to have two draw backs:
@ -376,6 +376,63 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
// is not correct. It confuses QPA's internal logic. // is not correct. It confuses QPA's internal logic.
clientRect->bottom += 1; clientRect->bottom += 1;
#endif #endif
// Dirty hack to workaround the DWM flicker.
LARGE_INTEGER freq = {};
if (QueryPerformanceFrequency(&freq) == FALSE) {
qWarning() << Utilities::getSystemErrorMessage(QStringLiteral("QueryPerformanceFrequency"));
break;
}
TIMECAPS tc = {};
if (timeGetDevCaps(&tc, sizeof(tc)) != MMSYSERR_NOERROR) {
qWarning() << "timeGetDevCaps() failed.";
break;
}
const UINT ms_granularity = tc.wPeriodMin;
if (timeBeginPeriod(ms_granularity) != TIMERR_NOERROR) {
qWarning() << "timeBeginPeriod() failed.";
break;
}
LARGE_INTEGER now0 = {};
if (QueryPerformanceCounter(&now0) == FALSE) {
qWarning() << Utilities::getSystemErrorMessage(QStringLiteral("QueryPerformanceCounter"));
break;
}
// ask DWM where the vertical blank falls
DWM_TIMING_INFO dti;
SecureZeroMemory(&dti, sizeof(dti));
dti.cbSize = sizeof(dti);
const HRESULT hr = DwmGetCompositionTimingInfo(nullptr, &dti);
if (FAILED(hr)) {
qWarning() << Utilities::getSystemErrorMessage(QStringLiteral("DwmGetCompositionTimingInfo"));
break;
}
LARGE_INTEGER now1 = {};
if (QueryPerformanceCounter(&now1) == FALSE) {
qWarning() << Utilities::getSystemErrorMessage(QStringLiteral("QueryPerformanceCounter"));
break;
}
// - DWM told us about SOME vertical blank
// - past or future, possibly many frames away
// - convert that into the NEXT vertical blank
const LONGLONG period = dti.qpcRefreshPeriod;
const LONGLONG dt = dti.qpcVBlank - now1.QuadPart;
LONGLONG w = 0, m = 0;
if (dt >= 0) {
w = dt / period;
} else {
// reach back to previous period
// - so m represents consistent position within phase
w = -1 + dt / period;
}
m = dt - (period * w);
Q_ASSERT(m >= 0);
Q_ASSERT(m < period);
const qreal m_ms = 1000.0 * static_cast<qreal>(m) / static_cast<qreal>(freq.QuadPart);
Sleep(static_cast<DWORD>(qRound(m_ms)));
if (timeEndPeriod(ms_granularity) != TIMERR_NOERROR) {
qWarning() << "timeEndPeriod() failed.";
break;
}
// We cannot return WVR_REDRAW otherwise Windows exhibits bugs where // We cannot return WVR_REDRAW otherwise Windows exhibits bugs where
// client pixels and child windows are mispositioned by the width/height // client pixels and child windows are mispositioned by the width/height
// of the upper-left nonclient area. // of the upper-left nonclient area.
@ -502,7 +559,8 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
const int resizeBorderThickness = Utilities::getSystemMetric(window, SystemMetric::ResizeBorderThickness, true); const int resizeBorderThickness = Utilities::getSystemMetric(window, SystemMetric::ResizeBorderThickness, true);
const int titleBarHeight = Utilities::getSystemMetric(window, SystemMetric::TitleBarHeight, true); const int titleBarHeight = Utilities::getSystemMetric(window, SystemMetric::TitleBarHeight, true);
bool isTitleBar = false; bool isTitleBar = false;
if (IsMaximized(msg->hwnd) || (window->windowState() == Qt::WindowFullScreen)) { const bool max = IsMaximized(msg->hwnd);
if (max || (window->windowState() == Qt::WindowFullScreen)) {
isTitleBar = (localMouse.y() >= 0) && (localMouse.y() <= titleBarHeight) isTitleBar = (localMouse.y() >= 0) && (localMouse.y() <= titleBarHeight)
&& (localMouse.x() >= 0) && (localMouse.x() <= windowWidth) && (localMouse.x() >= 0) && (localMouse.x() <= windowWidth)
&& !Utilities::isHitTestVisible(window); && !Utilities::isHitTestVisible(window);
@ -513,8 +571,8 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
&& !Utilities::isHitTestVisible(window); && !Utilities::isHitTestVisible(window);
} }
const bool isTop = localMouse.y() <= resizeBorderThickness; const bool isTop = localMouse.y() <= resizeBorderThickness;
const LRESULT hitTestResult = [clientRect, msg, isTitleBar, &localMouse, resizeBorderThickness, windowWidth, isTop, window]{ *result = [clientRect, isTitleBar, &localMouse, resizeBorderThickness, windowWidth, isTop, window, max](){
if (IsMaximized(msg->hwnd)) { if (max) {
if (isTitleBar) { if (isTitleBar) {
return HTCAPTION; return HTCAPTION;
} }
@ -559,7 +617,6 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
} }
return HTCLIENT; return HTCLIENT;
}(); }();
*result = hitTestResult;
return true; return true;
} }
case WM_SETICON: case WM_SETICON:
@ -591,14 +648,14 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
*result = ret; *result = ret;
return true; return true;
} }
case WM_WINDOWPOSCHANGING: {
#if (QT_VERSION < QT_VERSION_CHECK(6, 2, 2)) #if (QT_VERSION < QT_VERSION_CHECK(6, 2, 2))
case WM_WINDOWPOSCHANGING: {
// Tell Windows to discard the entire contents of the client area, as re-using // Tell Windows to discard the entire contents of the client area, as re-using
// parts of the client area would lead to jitter during resize. // parts of the client area would lead to jitter during resize.
const auto windowPos = reinterpret_cast<LPWINDOWPOS>(msg->lParam); const auto windowPos = reinterpret_cast<LPWINDOWPOS>(msg->lParam);
windowPos->flags |= SWP_NOCOPYBITS; windowPos->flags |= SWP_NOCOPYBITS;
#endif
} break; } break;
#endif
default: default:
break; break;
} }

View File

@ -77,6 +77,7 @@
#include <QtCore/qt_windows.h> #include <QtCore/qt_windows.h>
#include <shellapi.h> #include <shellapi.h>
#include <dwmapi.h> #include <dwmapi.h>
#include <timeapi.h>
#ifndef WM_NCUAHDRAWCAPTION #ifndef WM_NCUAHDRAWCAPTION
#define WM_NCUAHDRAWCAPTION (0x00AE) #define WM_NCUAHDRAWCAPTION (0x00AE)

View File

@ -9,7 +9,7 @@ DEFINES += \
QT_NO_CAST_TO_ASCII \ QT_NO_CAST_TO_ASCII \
QT_NO_KEYWORDS \ QT_NO_KEYWORDS \
QT_DEPRECATED_WARNINGS \ QT_DEPRECATED_WARNINGS \
QT_DISABLE_DEPRECATED_BEFORE=0x060100 \ QT_DISABLE_DEPRECATED_BEFORE=0x060200 \
FRAMELESSHELPER_BUILD_LIBRARY FRAMELESSHELPER_BUILD_LIBRARY
HEADERS += \ HEADERS += \
framelesshelper_global.h \ framelesshelper_global.h \
@ -32,6 +32,6 @@ win32 {
SOURCES += \ SOURCES += \
utilities_win32.cpp \ utilities_win32.cpp \
framelesshelper_win32.cpp framelesshelper_win32.cpp
LIBS += -luser32 -lshell32 -ldwmapi LIBS += -luser32 -lshell32 -ldwmapi -lwinmm
RC_FILE = framelesshelper.rc RC_FILE = framelesshelper.rc
} }