Skip to content

Commit

Permalink
Fix emulate message bug and git modules, window part mistakes, non cl…
Browse files Browse the repository at this point in the history
…ient leave mistakes and crash (wangwenx190#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
  • Loading branch information
SineStriker authored Aug 26, 2023
1 parent bdb1b5d commit 6ff0bc9
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 30 deletions.
2 changes: 1 addition & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[submodule "cmake"]
path = cmake
url = ../cmake-utils.git
url = ../../wangwenx190/cmake-utils.git
102 changes: 73 additions & 29 deletions src/core/framelesshelper_win.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<int> 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) };
Expand All @@ -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);
Expand Down Expand Up @@ -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) };
Expand Down Expand Up @@ -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;
}
}
Expand Down

0 comments on commit 6ff0bc9

Please sign in to comment.