diff --git a/CMakeLists.txt b/CMakeLists.txt index e66e8c5..8a25916 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -73,7 +73,7 @@ target_compile_definitions(${PROJECT_NAME} PRIVATE QT_NO_CAST_TO_ASCII QT_NO_KEYWORDS QT_DEPRECATED_WARNINGS - QT_DISABLE_DEPRECATED_BEFORE=0x060100 + QT_DISABLE_DEPRECATED_BEFORE=0x060200 FRAMELESSHELPER_BUILD_LIBRARY ) @@ -85,7 +85,7 @@ endif() if(WIN32) target_link_libraries(${PROJECT_NAME} PRIVATE - dwmapi + dwmapi winmm ) endif() diff --git a/examples/common.pri b/examples/common.pri index cb270c4..c3ec76e 100644 --- a/examples/common.pri +++ b/examples/common.pri @@ -5,12 +5,12 @@ DEFINES += \ QT_NO_CAST_TO_ASCII \ QT_NO_KEYWORDS \ QT_DEPRECATED_WARNINGS \ - QT_DISABLE_DEPRECATED_BEFORE=0x060100 + QT_DISABLE_DEPRECATED_BEFORE=0x060200 RESOURCES += $$PWD/images.qrc win32 { CONFIG += windeployqt CONFIG -= embed_manifest_exe - LIBS += -luser32 -lshell32 -ldwmapi + LIBS += -luser32 -lshell32 -ldwmapi -lwinmm RC_FILE = $$PWD/example.rc OTHER_FILES += $$PWD/example.manifest } diff --git a/examples/quick/qml/main.qml b/examples/quick/qml/main.qml index f2be90a..3a2a87b 100644 --- a/examples/quick/qml/main.qml +++ b/examples/quick/qml/main.qml @@ -1,5 +1,3 @@ - - /* * MIT License * @@ -23,6 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + import QtQuick 2.0 import QtQuick.Window 2.0 import QtQuick.Controls 2.0 @@ -48,13 +47,13 @@ Window { interval: 500 running: true repeat: true - onTriggered: label.text = Qt.formatTime(new Date(), "hh:mm:ss") + onTriggered: timeLabel.text = Qt.formatTime(new Date(), "hh:mm:ss") } Rectangle { id: titleBar height: framelessHelper.titleBarHeight - color: "transparent" + color: "white" anchors { top: parent.top topMargin: window._flh_margin @@ -106,7 +105,7 @@ Window { } Label { - id: label + id: timeLabel anchors.centerIn: parent font { pointSize: 70 @@ -115,9 +114,10 @@ Window { } Button { + id: fullScreenButton anchors { horizontalCenter: parent.horizontalCenter - top: label.bottom + top: timeLabel.bottom topMargin: 15 } property bool _full: window.visibility === Window.FullScreen diff --git a/framelesshelper_win32.cpp b/framelesshelper_win32.cpp index 589a5dd..c2459cb 100644 --- a/framelesshelper_win32.cpp +++ b/framelesshelper_win32.cpp @@ -245,10 +245,12 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me const auto clientRect = ((static_cast(msg->wParam) == FALSE) ? reinterpret_cast(msg->lParam) : &(reinterpret_cast(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 // have the WS_POPUP size, so we don't have to worry about // 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 // 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 @@ -267,7 +269,7 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me // Make sure to use MONITOR_DEFAULTTONEAREST, so that this will // still find the right monitor even when we're restoring from // minimized. - if (IsMaximized(msg->hwnd) || (window->windowState() == Qt::WindowFullScreen)) { + if (max || full) { APPBARDATA abd; SecureZeroMemory(&abd, sizeof(abd)); abd.cbSize = sizeof(abd); @@ -308,8 +310,6 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me left = hasAutohideTaskbar(ABE_LEFT); right = hasAutohideTaskbar(ABE_RIGHT); } else { - // The following code is copied from Mozilla Firefox, - // with some modifications. int edge = -1; APPBARDATA _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. // "clientRect->right += 1;" also works. // 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. clientRect->bottom += 1; #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(m) / static_cast(freq.QuadPart); + Sleep(static_cast(qRound(m_ms))); + if (timeEndPeriod(ms_granularity) != TIMERR_NOERROR) { + qWarning() << "timeEndPeriod() failed."; + break; + } // We cannot return WVR_REDRAW otherwise Windows exhibits bugs where // client pixels and child windows are mispositioned by the width/height // 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 titleBarHeight = Utilities::getSystemMetric(window, SystemMetric::TitleBarHeight, true); 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) && (localMouse.x() >= 0) && (localMouse.x() <= windowWidth) && !Utilities::isHitTestVisible(window); @@ -513,8 +571,8 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me && !Utilities::isHitTestVisible(window); } const bool isTop = localMouse.y() <= resizeBorderThickness; - const LRESULT hitTestResult = [clientRect, msg, isTitleBar, &localMouse, resizeBorderThickness, windowWidth, isTop, window]{ - if (IsMaximized(msg->hwnd)) { + *result = [clientRect, isTitleBar, &localMouse, resizeBorderThickness, windowWidth, isTop, window, max](){ + if (max) { if (isTitleBar) { return HTCAPTION; } @@ -559,7 +617,6 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me } return HTCLIENT; }(); - *result = hitTestResult; return true; } case WM_SETICON: @@ -591,14 +648,14 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me *result = ret; return true; } - case WM_WINDOWPOSCHANGING: { #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 // parts of the client area would lead to jitter during resize. const auto windowPos = reinterpret_cast(msg->lParam); windowPos->flags |= SWP_NOCOPYBITS; -#endif } break; +#endif default: break; } diff --git a/framelesshelper_windows.h b/framelesshelper_windows.h index 242dadc..a0f54be 100644 --- a/framelesshelper_windows.h +++ b/framelesshelper_windows.h @@ -77,6 +77,7 @@ #include #include #include +#include #ifndef WM_NCUAHDRAWCAPTION #define WM_NCUAHDRAWCAPTION (0x00AE) diff --git a/lib.pro b/lib.pro index 7030e3d..53e45f0 100644 --- a/lib.pro +++ b/lib.pro @@ -9,7 +9,7 @@ DEFINES += \ QT_NO_CAST_TO_ASCII \ QT_NO_KEYWORDS \ QT_DEPRECATED_WARNINGS \ - QT_DISABLE_DEPRECATED_BEFORE=0x060100 \ + QT_DISABLE_DEPRECATED_BEFORE=0x060200 \ FRAMELESSHELPER_BUILD_LIBRARY HEADERS += \ framelesshelper_global.h \ @@ -32,6 +32,6 @@ win32 { SOURCES += \ utilities_win32.cpp \ framelesshelper_win32.cpp - LIBS += -luser32 -lshell32 -ldwmapi + LIBS += -luser32 -lshell32 -ldwmapi -lwinmm RC_FILE = framelesshelper.rc }