From 5778d4540f0b522946c84c8ba601456f2d440f78 Mon Sep 17 00:00:00 2001
From: Sine Striker <trueful@163.com>
Date: 周三, 08 5月 2024 13:58:59 +0800
Subject: [PATCH] Fix a mistake

---
 src/core/contexts/win32windowcontext.cpp |  261 ++++++++++++++++++++++++++++++++++++++++-----------
 1 files changed, 202 insertions(+), 59 deletions(-)

diff --git a/src/core/contexts/win32windowcontext.cpp b/src/core/contexts/win32windowcontext.cpp
index 4fda1eb..9d37d7c 100644
--- a/src/core/contexts/win32windowcontext.cpp
+++ b/src/core/contexts/win32windowcontext.cpp
@@ -33,15 +33,13 @@
 #include "qwkglobal_p.h"
 #include "qwkwindowsextra_p.h"
 
-// https://github.com/qt/qtbase/blob/6.6.1/src/plugins/platforms/windows/qwindowswindow.cpp#L2791
-// https://github.com/qt/qtbase/blob/6.6.1/src/plugins/platforms/windows/qwindowswindow.cpp#L3321
-// This issue exists only in Qt 6.6.1, and was introduced by QTBUG-113736 patch and fixed by
-// QTBUG-117704 patch.
-#if !QWINDOWKIT_CONFIG(ENABLE_WINDOWS_SYSTEM_BORDERS) && QT_VERSION == QT_VERSION_CHECK(6, 6, 1)
-#  define QTBUG_113736_WORKAROUND
-#endif
-
 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;
@@ -241,14 +239,15 @@
         apis.ptimeEndPeriod(ms_granularity);
     }
 
-    static void showSystemMenu2(HWND hWnd, const POINT &pos, const bool selectFirstEntry,
-                                const bool fixedSize) {
+    // Returns false if the menu is canceled
+    static bool showSystemMenu_sys(HWND hWnd, const POINT &pos, const bool selectFirstEntry,
+                                   const bool fixedSize) {
         HMENU hMenu = ::GetSystemMenu(hWnd, FALSE);
         if (!hMenu) {
             // The corresponding window doesn't have a system menu, most likely due to the
             // lack of the "WS_SYSMENU" window style. This situation should not be treated
             // as an error so just ignore it and return early.
-            return;
+            return true;
         }
 
         const bool maxOrFull = IsMaximized(hWnd) || isFullScreen(hWnd);
@@ -292,7 +291,8 @@
         // Popup the system menu at the required position.
         const auto result = ::TrackPopupMenu(
             hMenu,
-            (TPM_RETURNCMD | (QGuiApplication::isRightToLeft() ? TPM_RIGHTALIGN : TPM_LEFTALIGN)),
+            (TPM_RETURNCMD | (QGuiApplication::isRightToLeft() ? TPM_RIGHTALIGN : TPM_LEFTALIGN) |
+             TPM_RIGHTBUTTON),
             pos.x, pos.y, 0, hWnd, nullptr);
 
         // Unhighlight the first menu item after the popup menu is closed, otherwise it will keep
@@ -301,11 +301,12 @@
 
         if (!result) {
             // The user canceled the menu, no need to continue.
-            return;
+            return false;
         }
 
         // Send the command that the user chooses to the corresponding window.
         ::PostMessageW(hWnd, WM_SYSCOMMAND, result, 0);
+        return true;
     }
 
     static inline Win32WindowContext::WindowPart getHitWindowPart(int hitTestResult) {
@@ -490,17 +491,38 @@
                 return false;
             }
 
-            // https://github.com/qt/qtbase/blob/e26a87f1ecc40bc8c6aa5b889fce67410a57a702/src/plugins/platforms/windows/qwindowscontext.cpp#L1546
-            // Qt needs to refer to the WM_NCCALCSIZE message data that hasn't been processed, so we
-            // have to process it after Qt acquires the initial data.
             auto msg = static_cast<const MSG *>(message);
-            if (msg->message == WM_NCCALCSIZE && lastMessageContext) {
-                LRESULT res;
-                if (lastMessageContext->nonClientCalcSizeHandler(msg->hwnd, msg->message,
-                                                                 msg->wParam, msg->lParam, &res)) {
-                    *result = decltype(*result)(res);
-                    return true;
+            switch (msg->message) {
+                case WM_NCCALCSIZE: {
+                    // https://github.com/qt/qtbase/blob/e26a87f1ecc40bc8c6aa5b889fce67410a57a702/src/plugins/platforms/windows/qwindowscontext.cpp#L1546
+                    // Qt needs to refer to the WM_NCCALCSIZE message data that hasn't been
+                    // processed, so we have to process it after Qt acquires the initial data.
+                    if (lastMessageContext) {
+                        LRESULT res;
+                        if (lastMessageContext->nonClientCalcSizeHandler(
+                                msg->hwnd, msg->message, msg->wParam, msg->lParam, &res)) {
+                            *result = decltype(*result)(res);
+                            return true;
+                        }
+                    }
+                    break;
                 }
+
+                    // case WM_NCHITTEST: {
+                    //     // The child window must return HTTRANSPARENT when processing
+                    //     WM_NCHITTEST for
+                    //     // the parent window to receive WM_NCHITTEST.
+                    //     if (!lastMessageContext) {
+                    //         auto rootHWnd = ::GetAncestor(msg->hwnd, GA_ROOT);
+                    //         if (rootHWnd != msg->hwnd) {
+                    //             if (auto ctx = g_wndProcHash->value(rootHWnd)) {
+                    //                 *result = HTTRANSPARENT;
+                    //                 return true;
+                    //             }
+                    //         }
+                    //     }
+                    //     break;
+                    // }
             }
             return false;
         }
@@ -589,13 +611,15 @@
             return ::DefWindowProcW(hWnd, message, wParam, lParam);
         }
 
+        WindowsNativeEventFilter::lastMessageContext = ctx;
+        const auto &contextCleaner = qScopeGuard([]() {
+            WindowsNativeEventFilter::lastMessageContext = nullptr; //
+        });
+
         // Since Qt does the necessary processing of the WM_NCCALCSIZE message, we need to
         // forward it right away and process it in our native event filter.
         if (message == WM_NCCALCSIZE) {
-            WindowsNativeEventFilter::lastMessageContext = ctx;
-            LRESULT result = ::CallWindowProcW(g_qtWindowProc, hWnd, message, wParam, lParam);
-            WindowsNativeEventFilter::lastMessageContext = nullptr;
-            return result;
+            return ::CallWindowProcW(g_qtWindowProc, hWnd, message, wParam, lParam);
         }
 
         // Try hooked procedure and save result
@@ -613,20 +637,10 @@
     }
 
     static inline void addManagedWindow(QWindow *window, HWND hWnd, Win32WindowContext *ctx) {
-#ifndef QTBUG_113736_WORKAROUND
-        const auto margins = [](HWND hWnd) -> QMargins {
-            const auto titleBarHeight = int(getTitleBarHeight(hWnd));
-            if (isSystemBorderEnabled()) {
-                return {0, -titleBarHeight, 0, 0};
-            } else {
-                const auto frameSize = int(getResizeBorderThickness(hWnd));
-                return {-frameSize, -titleBarHeight, -frameSize, -frameSize};
-            }
-        }(hWnd);
-
-        // Inform Qt we want and have set custom margins
-        setInternalWindowFrameMargins(window, margins);
-#endif
+        if (isSystemBorderEnabled()) {
+            // Inform Qt we want and have set custom margins
+            setInternalWindowFrameMargins(window, QMargins(0, -int(getTitleBarHeight(hWnd)), 0, 0));
+        }
 
         // Store original window proc
         if (!g_qtWindowProc) {
@@ -689,8 +703,8 @@
 #else
                 const QPoint nativeGlobalPos = QHighDpi::toNativePixels(pos, m_windowHandle);
 #endif
-                showSystemMenu2(hWnd, qpoint2point(nativeGlobalPos), false,
-                                m_delegate->isHostSizeFixed(m_host));
+                std::ignore = showSystemMenu_sys(hWnd, qpoint2point(nativeGlobalPos), false,
+                                                 m_delegate->isHostSizeFixed(m_host));
                 return;
             }
 
@@ -808,7 +822,6 @@
                        ? int(getWindowFrameBorderThickness(reinterpret_cast<HWND>(m_windowId)))
                        : 0;
         }
-
         return AbstractWindowContext::windowAttribute(key);
     }
 
@@ -816,11 +829,12 @@
         // Reset the context data
         mouseLeaveBlocked = false;
         lastHitTestResult = WindowPart::Outside;
+        lastHitTestResultRaw = HTNOWHERE;
 
-#ifdef QTBUG_113736_WORKAROUND
-        m_delegate->setWindowFlags(m_host,
-                                   m_delegate->getWindowFlags(m_host) | Qt::FramelessWindowHint);
-#endif
+        if (!isSystemBorderEnabled()) {
+            m_delegate->setWindowFlags(m_host, m_delegate->getWindowFlags(m_host) |
+                                                   Qt::FramelessWindowHint);
+        }
 
         // If the original window id is valid, remove all resources related
         if (oldWinId) {
@@ -1294,9 +1308,8 @@
     case WM_NCPOINTERUP:
 #endif
             case WM_NCMOUSEHOVER: {
-                const WindowPart currentWindowPart = lastHitTestResult;
                 if (message == WM_NCMOUSEMOVE) {
-                    if (currentWindowPart != WindowPart::ChromeButton) {
+                    if (lastHitTestResult != WindowPart::ChromeButton) {
                         // https://github.com/qt/qtbase/blob/e26a87f1ecc40bc8c6aa5b889fce67410a57a702/src/widgets/kernel/qwidgetwindow.cpp#L472
                         // When the mouse press event arrives, QWidgetWindow will implicitly grab
                         // the top widget right under the mouse, and set `qt_button_down` to this
@@ -1355,14 +1368,50 @@
                     }
                 }
 
-                if (currentWindowPart == WindowPart::ChromeButton) {
-                    emulateClientAreaMessage(hWnd, message, wParam, lParam);
+                if (lastHitTestResult == WindowPart::ChromeButton) {
                     if (message == WM_NCMOUSEMOVE) {
                         // ### FIXME FIXME FIXME
                         // ### FIXME: Calling DefWindowProc() here is really dangerous, investigate
                         // how to avoid doing this.
                         // ### FIXME FIXME FIXME
                         *result = ::DefWindowProcW(hWnd, WM_NCMOUSEMOVE, wParam, lParam);
+                        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, message, wParam, lParam);
+                                    iconButtonClickTime = 0;
+                                    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
                         // indicate we have handled these messages (due to historical reasons), for
@@ -1371,6 +1420,7 @@
                             (((message >= WM_NCXBUTTONDOWN) && (message <= WM_NCXBUTTONDBLCLK))
                                  ? TRUE
                                  : FALSE);
+                        emulateClientAreaMessage(hWnd, message, wParam, lParam);
                     }
                     return true;
                 }
@@ -1500,7 +1550,8 @@
                 // color, our homemade top border can almost have exactly the same
                 // appearance with the system's one.
                 [[maybe_unused]] const auto &hitTestRecorder = qScopeGuard([this, result]() {
-                    lastHitTestResult = getHitWindowPart(int(*result)); //
+                    lastHitTestResultRaw = int(*result);
+                    lastHitTestResult = getHitWindowPart(lastHitTestResultRaw);
                 });
 
                 POINT nativeGlobalPos{GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
@@ -2049,12 +2100,16 @@
         bool shouldShowSystemMenu = false;
         bool broughtByKeyboard = false;
         POINT nativeGlobalPos{};
+
         switch (message) {
             case WM_RBUTTONUP: {
                 const POINT nativeLocalPos = getNativePosFromMouse();
                 const QPoint qtScenePos =
                     QHighDpi::fromNativeLocalPosition(point2qpoint(nativeLocalPos), m_windowHandle);
-                if (isInTitleBarDraggableArea(qtScenePos)) {
+                WindowAgentBase::SystemButton sysButtonType = WindowAgentBase::Unknown;
+                if (isInTitleBarDraggableArea(qtScenePos) ||
+                    (isInSystemButtons(qtScenePos, &sysButtonType) &&
+                     sysButtonType == WindowAgentBase::WindowIcon)) {
                     shouldShowSystemMenu = true;
                     nativeGlobalPos = nativeLocalPos;
                     ::ClientToScreen(hWnd, &nativeGlobalPos);
@@ -2070,10 +2125,20 @@
             }
             case WM_SYSCOMMAND: {
                 const WPARAM filteredWParam = (wParam & 0xFFF0);
-                if ((filteredWParam == SC_KEYMENU) && (lParam == VK_SPACE)) {
-                    shouldShowSystemMenu = true;
-                    broughtByKeyboard = true;
-                    nativeGlobalPos = getNativeGlobalPosFromKeyboard();
+                switch (filteredWParam) {
+                    case SC_MOUSEMENU:
+                        shouldShowSystemMenu = true;
+                        nativeGlobalPos = getNativeGlobalPosFromKeyboard();
+                        break;
+                    case SC_KEYMENU:
+                        if (lParam == VK_SPACE) {
+                            shouldShowSystemMenu = true;
+                            broughtByKeyboard = true;
+                            nativeGlobalPos = getNativeGlobalPosFromKeyboard();
+                        }
+                        break;
+                    default:
+                        break;
                 }
                 break;
             }
@@ -2092,8 +2157,86 @@
                 break;
         }
         if (shouldShowSystemMenu) {
-            showSystemMenu2(hWnd, nativeGlobalPos, broughtByKeyboard,
-                            m_delegate->isHostSizeFixed(m_host));
+            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) {
+                                switch (wParam) {
+                                    case WM_LBUTTONDBLCLK:
+                                        mouseDoubleClicked = true;
+                                        Q_FALLTHROUGH();
+
+                                        // case WM_POINTERDOWN:
+
+                                    case WM_LBUTTONDOWN: {
+                                        auto pMouseStruct =
+                                            reinterpret_cast<MOUSEHOOKSTRUCT *>(lParam);
+                                        if (pMouseStruct) {
+                                            mouseClickPos = pMouseStruct->pt;
+                                        }
+                                        break;
+                                    }
+                                    default:
+                                        break;
+                                }
+                            }
+                            return ::CallNextHookEx(nullptr, nCode, wParam, lParam);
+                        },
+                        nullptr, ::GetCurrentThreadId());
+                    mouseHookedLocal = true;
+                }
+            }
+
+            bool res = showSystemMenu_sys(hWnd, nativeGlobalPos, broughtByKeyboard,
+                                          m_delegate->isHostSizeFixed(m_host));
+
+            // Uninstall mouse hook and check if it's a double-click
+            if (mouseHookedLocal) {
+                ::UnhookWindowsHookEx(mouseHook);
+
+                // Emulate the Windows icon button's behavior
+                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 <= ::GetDoubleClickTime()) {
+                            iconButtonClickLevel |= IconButtonTriggersClose;
+                        }
+                    }
+                }
+
+                if (mouseDoubleClicked) {
+                    iconButtonClickLevel |= IconButtonDoubleClicked;
+                }
+
+                mouseHook = nullptr;
+                mouseClickPos.reset();
+                mouseDoubleClicked = false;
+            }
+
             // QPA's internal code will handle system menu events separately, and its
             // behavior is not what we would want to see because it doesn't know our
             // window doesn't have any window frame now, so return early here to avoid

--
Gitblit v1.9.1