From 6ff0bc9300a7ce26c6b86700da79b08eaf61d245 Mon Sep 17 00:00:00 2001 From: SineStriker <55847490+SineStriker@users.noreply.github.com> Date: Sat, 26 Aug 2023 12:18:36 +0800 Subject: [PATCH] Fix emulate message bug and git modules, window part mistakes, non client leave mistakes and crash (#269) * Fix emulate message bug and git modules * Fix window part mistakes * Fix WM_NCMOUSELEAVE message mistakes and crash * Fix maximize minimize button hover problem --- .gitmodules | 2 +- src/core/framelesshelper_win.cpp | 102 ++++++++++++++++++++++--------- 2 files changed, 74 insertions(+), 30 deletions(-) diff --git a/.gitmodules b/.gitmodules index fc6d0543..7c83314c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "cmake"] path = cmake - url = ../cmake-utils.git + url = ../../wangwenx190/cmake-utils.git diff --git a/src/core/framelesshelper_win.cpp b/src/core/framelesshelper_win.cpp index 85b3b9c0..1e700534 100644 --- a/src/core/framelesshelper_win.cpp +++ b/src/core/framelesshelper_win.cpp @@ -313,9 +313,17 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me } const UINT uMsg = msg->message; // WM_QUIT won't be posted to the WindowProc function. - if ((uMsg == WM_CLOSE) || (uMsg == WM_DESTROY)) { + switch (uMsg) { + case WM_CLOSE: + case WM_DESTROY: + case WM_NCDESTROY: + case 144: + case 626: return false; + default: + break; } + const auto it = g_framelessWin32HelperData()->data.find(windowId); if (it == g_framelessWin32HelperData()->data.end()) { return false; @@ -345,19 +353,20 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me #endif // (QT_VERSION < QT_VERSION_CHECK(6, 5, 1)) const auto emulateClientAreaMessage = [hWnd, uMsg, wParam, lParam](const std::optional overrideMessage = std::nullopt) -> void { - const auto wparam = [uMsg, wParam]() -> WPARAM { - if (uMsg == WM_NCMOUSELEAVE) { + auto myMsg = overrideMessage.value_or(uMsg); + const auto wparam = [myMsg, wParam]() -> WPARAM { + if (myMsg == WM_NCMOUSELEAVE) { return kMessageTag; } const quint64 keyState = Utils::getKeyState(); - if ((uMsg >= WM_NCXBUTTONDOWN) && (uMsg <= WM_NCXBUTTONDBLCLK)) { + if ((myMsg >= WM_NCXBUTTONDOWN) && (myMsg <= WM_NCXBUTTONDBLCLK)) { const auto xButtonMask = GET_XBUTTON_WPARAM(wParam); return MAKEWPARAM(keyState, xButtonMask); } return keyState; }(); - const auto lparam = [uMsg, lParam, hWnd]() -> LPARAM { - if (uMsg == WM_NCMOUSELEAVE) { + const auto lparam = [myMsg, lParam, hWnd]() -> LPARAM { + if (myMsg == WM_NCMOUSELEAVE) { return 0; } const auto screenPos = POINT{ GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; @@ -373,7 +382,7 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me #else # define SEND_MESSAGE ::PostMessageW #endif - switch (overrideMessage.value_or(uMsg)) { + switch (myMsg) { case WM_NCHITTEST: // Treat hit test messages as mouse move events. case WM_NCMOUSEMOVE: SEND_MESSAGE(hWnd, WM_MOUSEMOVE, wparam, lparam); @@ -785,14 +794,12 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me const auto hitTestRecorder = qScopeGuard([&muData, &result](){ auto &first = std::get<0>(muData.hitTestResult); auto &second = std::get<1>(muData.hitTestResult); - if (first.has_value()) { - if (second.has_value()) { - std::swap(first, second); - } - second = *result; - } else { - first = *result; + if (second.has_value()) { + first = second; + } else if (!first.has_value()){ + first = HTNOWHERE; } + second = *result; }); const auto nativeGlobalPos = POINT{ GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; @@ -999,36 +1006,73 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me // The following code is not workaround anything, it's just try to emulate the original // behavior of a native Win32 window. const WindowPart previousWindowPart = getHittedWindowPart(data.hitTestResult.first.value_or(HTNOWHERE)); - const bool isXButtonMessage = ((uMsg >= WM_NCXBUTTONDOWN) && (uMsg <= WM_NCXBUTTONDBLCLK)); - const WindowPart nowWindowPart = getHittedWindowPart(isXButtonMessage ? GET_NCHITTEST_WPARAM(wParam) : wParam); + const WindowPart currentWindowPart = getHittedWindowPart(data.hitTestResult.second.value_or(HTNOWHERE)); if (uMsg == WM_NCMOUSELEAVE) { - if (nowWindowPart == WindowPart::NotInterested) { - std::ignore = listenForMouseLeave(hWnd, false); - } if (previousWindowPart == WindowPart::ChromeButton) { - if (nowWindowPart == WindowPart::ClientArea) { - *result = FALSE; + if (currentWindowPart == WindowPart::ClientArea) { + // Since we filter the WM_MOUSELEAVE event when the mouse is above the buttons, + // Qt will always think it's tracking the mouse. + // If we don't track the mouse after the mouse enter client area, there will + // be no WM_MOUSELEAVE sent by Windows which should be sent. + std::ignore = listenForMouseLeave(hWnd, false); + + // According to numerous experiments we've conducted, Windows maintains the mouse + // state internally, we must eventually let Windows handle the WM_NCMOUSELEAVE + // otherwise the internal state will be broken so that Windows may fail to send + // the WM_NCMOUSELEAVE messages which we need. + *result = ::DefWindowProcW(hWnd, uMsg, wParam, lParam); return true; - } else if (nowWindowPart == WindowPart::NotInterested) { + } else if (currentWindowPart == WindowPart::NotInterested) { emulateClientAreaMessage(WM_NCMOUSELEAVE); } } + + if (currentWindowPart == WindowPart::NotInterested) { + // The mouse is leaving window from non-client area, clear window part caches + auto &hitTestResult = muData.hitTestResult; + hitTestResult.first.reset(); + hitTestResult.second.reset(); + + // Notice: we're not going to clear window part caches when the mouse leaves window + // from client area, which means we will get previous window part as HTCLIENT if + // the mouse leaves window from client area and enters window from non-client area, + // but it has no bad effect. + } } else { if (uMsg == WM_NCMOUSEMOVE) { - if (nowWindowPart != WindowPart::ChromeButton) { + if (currentWindowPart != WindowPart::ChromeButton) { data.params.resetQtGrabbedControl(); } if ((previousWindowPart == WindowPart::ChromeButton) - && ((nowWindowPart == WindowPart::TitleBar) - || (nowWindowPart == WindowPart::ResizeBorder) - || (nowWindowPart == WindowPart::FixedBorder))) { + && ((currentWindowPart == WindowPart::TitleBar) + || (currentWindowPart == WindowPart::ResizeBorder) + || (currentWindowPart == WindowPart::FixedBorder))) { emulateClientAreaMessage(WM_NCMOUSELEAVE); } } - if (nowWindowPart == WindowPart::ChromeButton) { + + { + // We need to make sure we get the correct window part when a WM_NCMOUSELEAVE come, + // so we reset current window part to null when we receive a WM_NCMOUSEMOVE. + + // If the mouse is entering the client area, there must be a WM_NCHITTEST setting current + // window part to NCCLIENT before the WM_NCMOUSELEAVE comes; + // If the mouse is leaving the window, current window part remains as null. + auto &hitTestResult = muData.hitTestResult; + if (hitTestResult.second.has_value()) { + hitTestResult.first = hitTestResult.second; + hitTestResult.second.reset(); + } + } + + if (currentWindowPart == WindowPart::ChromeButton) { emulateClientAreaMessage(); - std::ignore = listenForMouseLeave(hWnd, true); - *result = (isXButtonMessage ? TRUE : FALSE); + if (uMsg == WM_NCMOUSEMOVE) { + // We should pass WM_NCMOUSEMOVE to Windows as well as WM_NCMOUSELEAVE + *result = ::DefWindowProcW(hWnd, uMsg, wParam, lParam); + } else { + *result = ((uMsg >= WM_NCXBUTTONDOWN) && (uMsg <= WM_NCXBUTTONDBLCLK)) ? TRUE : FALSE; + } return true; } }