From dd961a654f29fe6a59a093f8b4b0fff726efbd4a Mon Sep 17 00:00:00 2001
From: Sine Striker <trueful@163.com>
Date: 周三, 08 5月 2024 11:55:57 +0800
Subject: [PATCH] Fix a mistake

---
 src/core/contexts/win32windowcontext.cpp |  141 ++++++++++++++++++++++++++++++++++++-----------
 1 files changed, 108 insertions(+), 33 deletions(-)

diff --git a/src/core/contexts/win32windowcontext.cpp b/src/core/contexts/win32windowcontext.cpp
index 171a348..a448ed6 100644
--- a/src/core/contexts/win32windowcontext.cpp
+++ b/src/core/contexts/win32windowcontext.cpp
@@ -35,6 +35,12 @@
 
 namespace QWK {
 
+    enum IconButtonClickLevelFlag {
+        IconButtonClicked = 1,
+        IconButtonDoubleClicked = 2,
+        IconButtonTriggersClose = 4,
+    };
+
     // The thickness of an auto-hide taskbar in pixels.
     static constexpr const quint8 kAutoHideTaskBarThickness = 2;
 
@@ -1369,20 +1375,42 @@
                         // how to avoid doing this.
                         // ### FIXME FIXME FIXME
                         *result = ::DefWindowProcW(hWnd, WM_NCMOUSEMOVE, wParam, lParam);
-                    } else if (lastHitTestResultRaw == HTSYSMENU) {
-                        if (message == WM_NCLBUTTONDOWN) {
-                            // A message of WM_SYSCOMMAND with SC_MOUSEMENU will be sent by Windows,
-                            // and the current control flow will be blocked by the menu while
-                            // Windows will create and execute a new event loop until the menu
-                            // returns
-                            iconButtonClickTime = ::GetTickCount64();
-                            *result = ::DefWindowProcW(hWnd, message, wParam, lParam);
-                            iconButtonClickTime = 0;
-                        } else if (message == WM_NCLBUTTONDBLCLK) {
-                            // A message of WM_SYSCOMMAND with SC_CLOSE will be sent by Windows
-                            *result = ::DefWindowProcW(hWnd, message, wParam, lParam);
-                        } else {
-                            *result = FALSE;
+                        emulateClientAreaMessage(hWnd, message, wParam, lParam);
+                        return true;
+                    }
+
+                    if (lastHitTestResultRaw == HTSYSMENU) {
+                        switch (message) {
+                            case WM_NCLBUTTONDOWN:
+                                if (iconButtonClickLevel == 0) {
+                                    // A message of WM_SYSCOMMAND with SC_MOUSEMENU will be sent by
+                                    // Windows, and the current control flow will be blocked by the
+                                    // menu while Windows will create and execute a new event loop
+                                    // until the menu returns
+                                    iconButtonClickTime = ::GetTickCount64();
+                                    *result =
+                                        ::DefWindowProcW(hWnd, WM_NCLBUTTONDOWN, wParam, lParam);
+                                    if (iconButtonClickLevel & IconButtonTriggersClose) {
+                                        ::PostMessageW(hWnd, WM_SYSCOMMAND, SC_CLOSE, 0);
+                                    }
+                                    if (iconButtonClickLevel & IconButtonDoubleClicked) {
+                                        iconButtonClickLevel = 0;
+                                    }
+                                    // Otherwise, no need to reset `iconButtonClickLevel` if not to
+                                    // close, if it has value, there must be another incoming
+                                    // WM_NCLBUTTONDOWN
+                                } else {
+                                    iconButtonClickLevel = 0;
+                                }
+                                break;
+                            case WM_NCLBUTTONDBLCLK:
+                                // A message of WM_SYSCOMMAND with SC_CLOSE will be sent by Windows
+                                *result = ::DefWindowProcW(hWnd, message, wParam, lParam);
+                                break;
+                            default:
+                                *result = FALSE;
+                                emulateClientAreaMessage(hWnd, message, wParam, lParam);
+                                break;
                         }
                     } else {
                         // According to MSDN, we should return non-zero for X button messages to
@@ -1392,8 +1420,8 @@
                             (((message >= WM_NCXBUTTONDOWN) && (message <= WM_NCXBUTTONDBLCLK))
                                  ? TRUE
                                  : FALSE);
+                        emulateClientAreaMessage(hWnd, message, wParam, lParam);
                     }
-                    emulateClientAreaMessage(hWnd, message, wParam, lParam);
                     return true;
                 }
                 break;
@@ -2126,29 +2154,76 @@
                 break;
         }
         if (shouldShowSystemMenu) {
-            bool triggeredByIconButton = iconButtonClickTime > 0;
-            if (triggeredByIconButton) {
-                // TODO: Adjust `nativeGlobalPos` to (0, realTitleBarHeight)
-                // hint: use m_delgegate->mapGeometryToScene
+            static HHOOK mouseHook = nullptr;
+            static std::optional<POINT> mouseClickPos;
+            static bool mouseDoubleClicked = false;
+            bool mouseHookedLocal = false;
+
+            // The menu is triggered by a click on icon button
+            if (iconButtonClickTime > 0) {
+                POINT menuPos{0, static_cast<LONG>(getTitleBarHeight(hWnd))};
+                if (const auto tb = titleBar()) {
+                    auto titleBarHeight = qreal(m_delegate->mapGeometryToScene(tb).height());
+                    titleBarHeight *= m_windowHandle->devicePixelRatio();
+                    menuPos.y = qRound(titleBarHeight);
+                }
+                ::ClientToScreen(hWnd, &menuPos);
+                nativeGlobalPos = menuPos;
+
+                // Install mouse hook
+                if (!mouseHook) {
+                    mouseHook = ::SetWindowsHookExW(
+                        WH_MOUSE,
+                        [](int nCode, WPARAM wParam, LPARAM lParam) {
+                            if (nCode >= 0) {
+                                if (wParam == WM_LBUTTONDOWN || wParam == WM_LBUTTONDBLCLK) {
+                                    if (wParam == WM_LBUTTONDBLCLK) {
+                                        mouseDoubleClicked = true;
+                                    }
+                                    auto pMouseStruct = reinterpret_cast<MOUSEHOOKSTRUCT *>(lParam);
+                                    if (pMouseStruct) {
+                                        mouseClickPos = pMouseStruct->pt;
+                                    }
+                                }
+                            }
+                            return ::CallNextHookEx(nullptr, nCode, wParam, lParam);
+                        },
+                        nullptr, ::GetCurrentThreadId());
+                    mouseHookedLocal = true;
+                }
             }
+
             bool res = showSystemMenu_sys(hWnd, nativeGlobalPos, broughtByKeyboard,
                                           m_delegate->isHostSizeFixed(m_host));
 
-            // Emulate the Windows SYSMENU button's behavior
-            static uint32_t doubleClickTime = ::GetDoubleClickTime();
-            if (triggeredByIconButton && !res &&
-                ::GetTickCount64() - iconButtonClickTime <= doubleClickTime) {
-                POINT nativeGlobalPos;
-                ::GetCursorPos(&nativeGlobalPos);
-                POINT nativeLocalPos = nativeGlobalPos;
-                ::ScreenToClient(hWnd, &nativeLocalPos);
-                QPoint qtScenePos =
-                    QHighDpi::fromNativeLocalPosition(point2qpoint(nativeLocalPos), m_windowHandle);
-                WindowAgentBase::SystemButton sysButtonType = WindowAgentBase::Unknown;
-                if (isInSystemButtons(qtScenePos, &sysButtonType) &&
-                    sysButtonType == WindowAgentBase::WindowIcon) {
-                    ::PostMessageW(hWnd, WM_SYSCOMMAND, SC_CLOSE, 0);
+            // Uninstall mouse hook and check if it's a double-click
+            if (mouseHookedLocal) {
+                ::UnhookWindowsHookEx(mouseHook);
+
+                // Emulate the Windows icon button's behavior
+                static uint32_t doubleClickTime = ::GetDoubleClickTime();
+                if (!res && mouseClickPos.has_value()) {
+                    POINT nativeLocalPos = mouseClickPos.value();
+                    ::ScreenToClient(hWnd, &nativeLocalPos);
+                    QPoint qtScenePos = QHighDpi::fromNativeLocalPosition(
+                        point2qpoint(nativeLocalPos), m_windowHandle);
+                    WindowAgentBase::SystemButton sysButtonType = WindowAgentBase::Unknown;
+                    if (isInSystemButtons(qtScenePos, &sysButtonType) &&
+                        sysButtonType == WindowAgentBase::WindowIcon) {
+                        iconButtonClickLevel |= IconButtonClicked;
+                        if (::GetTickCount64() - iconButtonClickTime <= doubleClickTime) {
+                            iconButtonClickLevel |= IconButtonTriggersClose;
+                        }
+                    }
                 }
+
+                if (mouseDoubleClicked) {
+                    iconButtonClickLevel |= IconButtonDoubleClicked;
+                }
+
+                mouseHook = nullptr;
+                mouseClickPos.reset();
+                mouseDoubleClicked = false;
             }
 
             // QPA's internal code will handle system menu events separately, and its

--
Gitblit v1.9.1