From 1b9ac9ea6998ac8a4e51413e06aeed859e784b26 Mon Sep 17 00:00:00 2001
From: Sine Striker <trueful@163.com>
Date: 周一, 11 12月 2023 20:09:19 +0800
Subject: [PATCH] minor tweaks

---
 src/core/contexts/win32windowcontext.cpp |  494 ++++++++++++++++++++++++++++++++++++++++++------------
 1 files changed, 381 insertions(+), 113 deletions(-)

diff --git a/src/core/contexts/win32windowcontext.cpp b/src/core/contexts/win32windowcontext.cpp
index 1201277..ccc318d 100644
--- a/src/core/contexts/win32windowcontext.cpp
+++ b/src/core/contexts/win32windowcontext.cpp
@@ -1,14 +1,15 @@
 #include "win32windowcontext_p.h"
-#include "qwkcoreglobal_p.h"
 
 #include <optional>
 
 #include <QtCore/QHash>
-#include <QtCore/QAbstractNativeEventFilter>
-#include <QtCore/QOperatingSystemVersion>
 #include <QtCore/QScopeGuard>
 #include <QtGui/QGuiApplication>
+#include <QtGui/QPainter>
+#include <QtGui/QPalette>
+#include <QtGui/QStyleHints>
 
+#include <QtCore/private/qwinregistry_p.h>
 #include <QtCore/private/qsystemlibrary_p.h>
 #include <QtGui/private/qhighdpiscaling_p.h>
 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
@@ -25,18 +26,36 @@
 #include <dwmapi.h>
 #include <timeapi.h>
 
+#include "nativeeventfilter.h"
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
 Q_DECLARE_METATYPE(QMargins)
+#endif
 
 namespace QWK {
 
-    static constexpr const auto kAutoHideTaskBarThickness =
-        quint8{2}; // The thickness of an auto-hide taskbar in pixels.
+    // The thickness of an auto-hide taskbar in pixels.
+    static constexpr const auto kAutoHideTaskBarThickness = quint8{2};
 
-    using WndProcHash = QHash<HWND, Win32WindowContext *>; // hWnd -> context
+    static inline constexpr const auto kFrameBorderActiveColorLight =
+        QColor{110, 110, 110};                                                           // #6E6E6E
+    static inline constexpr const auto kFrameBorderActiveColorDark = QColor{51, 51, 51}; // #333333
+    static inline constexpr const auto kFrameBorderInactiveColorLight =
+        QColor{167, 167, 167};                                                           // #A7A7A7
+    static inline constexpr const auto kFrameBorderInactiveColorDark =
+        QColor{61, 61, 62};                                                              // #3D3D3E
+
+    // hWnd -> context
+    using WndProcHash = QHash<HWND, Win32WindowContext *>;
     Q_GLOBAL_STATIC(WndProcHash, g_wndProcHash)
 
-    static WNDPROC g_qtWindowProc = nullptr; // Original Qt window proc function
+    // Original Qt window proc function
+    static WNDPROC g_qtWindowProc = nullptr;
 
+    // ### FIXME FIXME FIXME
+    // ### FIXME: Tell the user to call in the documentation, instead of automatically
+    // calling it directly.
+    // ### FIXME FIXME FIXME
     static struct QWK_Hook {
         QWK_Hook() {
             qApp->setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
@@ -44,37 +63,54 @@
     } g_hook{};
 
     struct DynamicApis {
-        decltype(&::DwmFlush) pDwmFlush = nullptr;
-        decltype(&::DwmIsCompositionEnabled) pDwmIsCompositionEnabled = nullptr;
-        decltype(&::DwmGetCompositionTimingInfo) pDwmGetCompositionTimingInfo = nullptr;
-        decltype(&::GetDpiForWindow) pGetDpiForWindow = nullptr;
-        decltype(&::GetSystemMetricsForDpi) pGetSystemMetricsForDpi = nullptr;
-        decltype(&::GetDpiForMonitor) pGetDpiForMonitor = nullptr;
-        decltype(&::timeGetDevCaps) ptimeGetDevCaps = nullptr;
-        decltype(&::timeBeginPeriod) ptimeBeginPeriod = nullptr;
-        decltype(&::timeEndPeriod) ptimeEndPeriod = nullptr;
+//        template <typename T>
+//        struct DefaultFunc;
+//
+//        template <typename Return, typename... Args>
+//        struct DefaultFunc<Return(QT_WIN_CALLBACK *)(Args...)> {
+//            static Return STDAPICALLTYPE func(Args...) {
+//                return Return{};
+//            }
+//        };
+//
+// #define DYNAMIC_API_DECLARE(NAME) decltype(&::NAME) p##NAME =
+// DefaultFunc<decltype(&::NAME)>::func
+#define DYNAMIC_API_DECLARE(NAME) decltype(&::NAME) p##NAME = nullptr
+
+        DYNAMIC_API_DECLARE(DwmFlush);
+        DYNAMIC_API_DECLARE(DwmIsCompositionEnabled);
+        DYNAMIC_API_DECLARE(DwmGetCompositionTimingInfo);
+        DYNAMIC_API_DECLARE(GetDpiForWindow);
+        DYNAMIC_API_DECLARE(GetSystemMetricsForDpi);
+        DYNAMIC_API_DECLARE(GetDpiForMonitor);
+        DYNAMIC_API_DECLARE(timeGetDevCaps);
+        DYNAMIC_API_DECLARE(timeBeginPeriod);
+        DYNAMIC_API_DECLARE(timeEndPeriod);
+
+#undef DYNAMIC_API_DECLARE
 
         DynamicApis() {
-            QSystemLibrary user32(QStringLiteral("user32.dll"));
-            pGetDpiForWindow =
-                reinterpret_cast<decltype(pGetDpiForWindow)>(user32.resolve("GetDpiForWindow"));
-            pGetSystemMetricsForDpi = reinterpret_cast<decltype(pGetSystemMetricsForDpi)>(
-                user32.resolve("GetSystemMetricsForDpi"));
+#define DYNAMIC_API_RESOLVE(DLL, NAME)                                                             \
+    p##NAME = reinterpret_cast<decltype(p##NAME)>(DLL.resolve(#NAME))
 
-            QSystemLibrary shcore(QStringLiteral("shcore.dll"));
-            pGetDpiForMonitor =
-                reinterpret_cast<decltype(pGetDpiForMonitor)>(shcore.resolve("GetDpiForMonitor"));
+            QSystemLibrary user32(QStringLiteral("user32"));
+            DYNAMIC_API_RESOLVE(user32, GetDpiForWindow);
+            DYNAMIC_API_RESOLVE(user32, GetSystemMetricsForDpi);
 
-            QSystemLibrary dwmapi(QStringLiteral("dwmapi.dll"));
-            pDwmFlush = reinterpret_cast<decltype(pDwmFlush)>(dwmapi.resolve("DwmFlush"));
-            pDwmIsCompositionEnabled = reinterpret_cast<decltype(pDwmIsCompositionEnabled)>(
-                dwmapi.resolve("DwmIsCompositionEnabled"));
-            pDwmGetCompositionTimingInfo = reinterpret_cast<decltype(pDwmGetCompositionTimingInfo)>(dwmapi.resolve("DwmGetCompositionTimingInfo"));
+            QSystemLibrary shcore(QStringLiteral("shcore"));
+            DYNAMIC_API_RESOLVE(shcore, GetDpiForMonitor);
 
-            QSystemLibrary winmm(QStringLiteral("winmm.dll"));
-            ptimeGetDevCaps = reinterpret_cast<decltype(ptimeGetDevCaps)>(winmm.resolve("timeGetDevCaps"));
-            ptimeBeginPeriod = reinterpret_cast<decltype(ptimeBeginPeriod)>(winmm.resolve("timeBeginPeriod"));
-            ptimeEndPeriod = reinterpret_cast<decltype(ptimeEndPeriod)>(winmm.resolve("timeEndPeriod"));
+            QSystemLibrary dwmapi(QStringLiteral("dwmapi"));
+            DYNAMIC_API_RESOLVE(dwmapi, DwmFlush);
+            DYNAMIC_API_RESOLVE(dwmapi, DwmIsCompositionEnabled);
+            DYNAMIC_API_RESOLVE(dwmapi, DwmGetCompositionTimingInfo);
+
+            QSystemLibrary winmm(QStringLiteral("winmm"));
+            DYNAMIC_API_RESOLVE(winmm, timeGetDevCaps);
+            DYNAMIC_API_RESOLVE(winmm, timeBeginPeriod);
+            DYNAMIC_API_RESOLVE(winmm, timeEndPeriod);
+
+#undef DYNAMIC_API_RESOLVE
         }
 
         ~DynamicApis() = default;
@@ -169,20 +205,22 @@
     }
 
     static inline bool isWin8OrGreater() {
-        static const bool result =
-            QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows8;
+        static const bool result = IsWindows8OrGreater_Real();
         return result;
     }
 
     static inline bool isWin8Point1OrGreater() {
-        static const bool result =
-            QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows8_1;
+        static const bool result = IsWindows8Point1OrGreater_Real();
         return result;
     }
 
     static inline bool isWin10OrGreater() {
-        static const bool result =
-            QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows10;
+        static const bool result = IsWindows10OrGreater_Real();
+        return result;
+    }
+
+    static inline bool isWin11OrGreater() {
+        static const bool result = IsWindows11OrGreater_Real();
         return result;
     }
 
@@ -198,6 +236,57 @@
         return SUCCEEDED(apis.pDwmIsCompositionEnabled(&enabled)) && enabled;
     }
 
+    static inline bool isWindowFrameBorderColorized() {
+        const QWinRegistryKey registry(HKEY_CURRENT_USER, LR"(Software\Microsoft\Windows\DWM)");
+        if (!registry.isValid()) {
+            return false;
+        }
+        const auto value = registry.dwordValue(L"ColorPrevalence");
+        if (!value.second) {
+            return false;
+        }
+        return value.first;
+    }
+
+    static inline bool isDarkThemeActive() {
+#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
+        return QGuiApplication::styleHints()->colorScheme() == Qt::ColorScheme::Dark;
+#else
+        const QWinRegistryKey registry(
+            HKEY_CURRENT_USER, LR"(Software\Microsoft\Windows\CurrentVersion\Themes\Personalize)");
+        if (!registry.isValid()) {
+            return false;
+        }
+        const auto value = registry.dwordValue(L"AppsUseLightTheme");
+        if (!value.second) {
+            return false;
+        }
+        return !value.first;
+#endif
+    }
+
+    static inline QColor getAccentColor() {
+#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
+        return QGuiApplication::palette().color(QPalette::Accent);
+#else
+        const QWinRegistryKey registry(HKEY_CURRENT_USER, LR"(Software\Microsoft\Windows\DWM)");
+        if (!registry.isValid()) {
+            return {};
+        }
+        const auto value = registry.dwordValue(L"AccentColor");
+        if (!value.second) {
+            return {};
+        }
+        // The retrieved value is in the #AABBGGRR format, we need to
+        // convert it to the #AARRGGBB format which Qt expects.
+        const QColor abgr = QColor::fromRgba(value.first);
+        if (!abgr.isValid()) {
+            return {};
+        }
+        return QColor::fromRgb(abgr.blue(), abgr.green(), abgr.red(), abgr.alpha());
+#endif
+    }
+
     static inline void triggerFrameChange(HWND hwnd) {
         ::SetWindowPos(hwnd, nullptr, 0, 0, 0, 0,
                        SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER |
@@ -210,8 +299,8 @@
             return apis.pGetDpiForWindow(hwnd);
         } else if (apis.pGetDpiForMonitor) { // Win8.1
             HMONITOR monitor = ::MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
-            UINT dpiX{USER_DEFAULT_SCREEN_DPI};
-            UINT dpiY{USER_DEFAULT_SCREEN_DPI};
+            UINT dpiX{0};
+            UINT dpiY{0};
             apis.pGetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY);
             return dpiX;
         } else { // Win2K
@@ -262,7 +351,7 @@
 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
         if (QPlatformWindow *platformWindow = window->handle()) {
             if (const auto ni = QGuiApplication::platformNativeInterface()) {
-                ni->setWindowProperty(platformWindow, QStringLiteral("_q_windowsCustomMargins"),
+                ni->setWindowProperty(platformWindow, QStringLiteral("WindowsCustomMargins"),
                                       marginsVar);
             }
         }
@@ -295,10 +384,6 @@
     }
 
     static inline bool isFullScreen(HWND hwnd) {
-        Q_ASSERT(hwnd);
-        if (!hwnd) {
-            return false;
-        }
         RECT windowRect{};
         ::GetWindowRect(hwnd, &windowRect);
         // Compare to the full area of the screen, not the work area.
@@ -306,10 +391,6 @@
     }
 
     static inline bool isWindowNoState(HWND hwnd) {
-        Q_ASSERT(hwnd);
-        if (!hwnd) {
-            return false;
-        }
 #if 0
         WINDOWPLACEMENT wp{};
         wp.length = sizeof(wp);
@@ -358,8 +439,6 @@
             w = (ratio - qreal(1));
         }
         m = (dt - (period * w));
-        //Q_ASSERT((m > qreal(0)) || qFuzzyIsNull(m));
-        //Q_ASSERT(m < period);
         if ((m < qreal(0)) || qFuzzyCompare(m, period) || (m > period)) {
             return;
         }
@@ -368,16 +447,71 @@
         apis.ptimeEndPeriod(ms_granularity);
     }
 
-    static inline QPoint fromNativeLocalPosition(const QWindow *window, const QPoint &point) {
-        Q_ASSERT(window);
-        if (!window) {
-            return point;
+    static inline void showSystemMenu2(HWND hWnd, const POINT &pos, const bool selectFirstEntry,
+                                       const bool fixedSize) {
+        const 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;
         }
-#if 1
-        return QHighDpi::fromNativeLocalPosition(point, window);
-#else
-        return QPointF(QPointF(point) / window->devicePixelRatio()).toPoint();
-#endif
+
+        const bool maxOrFull = IsMaximized(hWnd) || isFullScreen(hWnd);
+        ::EnableMenuItem(hMenu, SC_CLOSE, (MF_BYCOMMAND | MFS_ENABLED));
+        ::EnableMenuItem(hMenu, SC_MAXIMIZE,
+                         (MF_BYCOMMAND | ((maxOrFull || fixedSize) ? MFS_DISABLED : MFS_ENABLED)));
+        ::EnableMenuItem(hMenu, SC_RESTORE,
+                         (MF_BYCOMMAND | ((maxOrFull && !fixedSize) ? MFS_ENABLED : MFS_DISABLED)));
+        // The first menu item should be selected by default if the menu is brought
+        // up by keyboard. I don't know how to pre-select a menu item but it seems
+        // highlight can do the job. However, there's an annoying issue if we do
+        // this manually: the highlighted menu item is really only highlighted,
+        // not selected, so even if the mouse cursor hovers on other menu items
+        // or the user navigates to other menu items through keyboard, the original
+        // highlight bar will not move accordingly, the OS will generate another
+        // highlight bar to indicate the current selected menu item, which will make
+        // the menu look kind of weird. Currently I don't know how to fix this issue.
+        ::HiliteMenuItem(hWnd, hMenu, SC_RESTORE,
+                         (MF_BYCOMMAND | (selectFirstEntry ? MFS_HILITE : MFS_UNHILITE)));
+        ::EnableMenuItem(hMenu, SC_MINIMIZE, (MF_BYCOMMAND | MFS_ENABLED));
+        ::EnableMenuItem(hMenu, SC_SIZE,
+                         (MF_BYCOMMAND | ((maxOrFull || fixedSize) ? MFS_DISABLED : MFS_ENABLED)));
+        ::EnableMenuItem(hMenu, SC_MOVE, (MF_BYCOMMAND | (maxOrFull ? MFS_DISABLED : MFS_ENABLED)));
+
+        // The default menu item will appear in bold font. There can only be one default
+        // menu item per menu at most. Set the item ID to "UINT_MAX" (or simply "-1")
+        // can clear the default item for the given menu.
+        UINT defaultItemId = UINT_MAX;
+        if (isWin11OrGreater()) {
+            if (maxOrFull) {
+                defaultItemId = SC_RESTORE;
+            } else {
+                defaultItemId = SC_MAXIMIZE;
+            }
+        }
+        if (defaultItemId == UINT_MAX) {
+            defaultItemId = SC_CLOSE;
+        }
+        ::SetMenuDefaultItem(hMenu, defaultItemId, FALSE);
+
+        // Popup the system menu at the required position.
+        const auto result = ::TrackPopupMenu(
+            hMenu,
+            (TPM_RETURNCMD | (QGuiApplication::isRightToLeft() ? TPM_RIGHTALIGN : TPM_LEFTALIGN)),
+            pos.x, pos.y, 0, hWnd, nullptr);
+
+        // Unhighlight the first menu item after the popup menu is closed, otherwise it will keep
+        // highlighting until we unhighlight it manually.
+        ::HiliteMenuItem(hWnd, hMenu, SC_RESTORE, (MF_BYCOMMAND | MFS_UNHILITE));
+
+        if (!result) {
+            // The user canceled the menu, no need to continue.
+            return;
+        }
+
+        // Send the command that the user chooses to the corresponding window.
+        ::PostMessageW(hWnd, WM_SYSCOMMAND, result, 0);
     }
 
     static inline Win32WindowContext::WindowPart getHitWindowPart(int hitTestResult) {
@@ -404,7 +538,8 @@
             case HTBORDER:
                 return Win32WindowContext::FixedBorder;
             default:
-                break; // unreachable
+                // unreachable
+                break;
         }
         return Win32WindowContext::Outside;
     }
@@ -448,14 +583,11 @@
     // DefWindowProc(). Consequently, we have to add a global native filter that forwards the result
     // of the hook function, telling Qt whether we have filtered the events before. Since Qt only
     // handles Windows window messages in the main thread, it is safe to do so.
-    class WindowsNativeEventFilter : public QAbstractNativeEventFilter {
+    class WindowsNativeEventFilter : public NativeEventFilter {
     public:
         bool nativeEventFilter(const QByteArray &eventType, void *message,
                                QT_NATIVE_EVENT_RESULT_TYPE *result) override {
             Q_UNUSED(eventType)
-
-            auto orgLastMessageContext = lastMessageContext;
-            lastMessageContext = nullptr;
 
             // It has been observed that the pointer that Qt gives us is sometimes null on some
             // machines. We need to guard against it in such scenarios.
@@ -465,11 +597,11 @@
 
             // 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 acquired the initial data.
+            // have to process it after Qt acquires the initial data.
             auto msg = static_cast<const MSG *>(message);
-            if (msg->message == WM_NCCALCSIZE && orgLastMessageContext) {
+            if (msg->message == WM_NCCALCSIZE && lastMessageContext) {
                 LRESULT res;
-                if (Win32WindowContext::nonClientCalcSizeHandler(msg->hwnd, msg->message,
+                if (lastMessageContext->nonClientCalcSizeHandler(msg->hwnd, msg->message,
                                                                  msg->wParam, msg->lParam, &res)) {
                     *result = decltype(*result)(res);
                     return true;
@@ -482,12 +614,16 @@
         static Win32WindowContext *lastMessageContext;
 
         static inline void install() {
+            if (instance) {
+                return;
+            }
             instance = new WindowsNativeEventFilter();
-            installNativeEventFilter(instance);
         }
 
         static inline void uninstall() {
-            removeNativeEventFilter(instance);
+            if (!instance) {
+                return;
+            }
             delete instance;
             instance = nullptr;
         }
@@ -565,22 +701,18 @@
         // forward it right away and process it in our native event filter.
         if (message == WM_NCCALCSIZE) {
             WindowsNativeEventFilter::lastMessageContext = ctx;
-            return ::CallWindowProcW(g_qtWindowProc, hWnd, message, wParam, lParam);
+            LRESULT result = ::CallWindowProcW(g_qtWindowProc, hWnd, message, wParam, lParam);
+            WindowsNativeEventFilter::lastMessageContext = nullptr;
+            return result;
         }
 
         // Try hooked procedure and save result
         LRESULT result;
-        bool handled = ctx->windowProc(hWnd, message, wParam, lParam, &result);
-
-        // TODO: Determine whether to show system menu
-        // ...
-
-        if (handled) {
+        if (ctx->windowProc(hWnd, message, wParam, lParam, &result)) {
             return result;
         }
 
         // Continue dispatching.
-        WindowsNativeEventFilter::lastMessageContext = ctx;
         return ::CallWindowProcW(g_qtWindowProc, hWnd, message, wParam, lParam);
     }
 
@@ -597,6 +729,41 @@
                 WindowsNativeEventFilter::uninstall();
             }
         }
+    }
+
+    QString Win32WindowContext::key() const {
+        return "win32";
+    }
+
+    void Win32WindowContext::virtual_hook(int id, void *data) {
+        switch (id) {
+            case ShowSystemMenuHook: {
+                const auto &pos = *reinterpret_cast<const QPoint *>(data);
+                auto winId = m_windowHandle->winId();
+                auto hWnd = reinterpret_cast<HWND>(winId);
+                showSystemMenu2(hWnd, qpoint2point(pos), false,
+                                m_delegate->isHostSizeFixed(m_host));
+                return;
+            }
+            case NeedsDrawBordersHook: {
+                auto &result = *reinterpret_cast<bool *>(data);
+                result = isWin10OrGreater() && !isWin11OrGreater();
+                return;
+            }
+            case DrawBordersHook: {
+                auto args = reinterpret_cast<void **>(data);
+                auto &painter = *reinterpret_cast<QPainter *>(args[0]);
+                auto &rect = *reinterpret_cast<const QRect *>(args[1]);
+                auto &region = *reinterpret_cast<const QRegion *>(args[2]);
+                // ### TODO
+                return;
+            }
+            default: {
+                // unreachable
+                break;
+            }
+        }
+        AbstractWindowContext::virtual_hook(id, data);
     }
 
     bool Win32WindowContext::setupHost() {
@@ -616,9 +783,7 @@
         ::SetWindowLongPtrW(hWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(QWKHookedWndProc));
 
         // Install global native event filter
-        if (!WindowsNativeEventFilter::instance) {
-            WindowsNativeEventFilter::install();
-        }
+        WindowsNativeEventFilter::install();
 
         // Cache window ID
         windowId = winId;
@@ -661,14 +826,17 @@
             return true;
         }
 
+        if (systemMenuHandler(hWnd, message, wParam, lParam, result)) {
+            return true;
+        }
+
         return false; // Not handled
     }
 
-    static constexpr const auto kMessageTag = WPARAM(0x97CCEA99);
-
-    static inline constexpr bool isTaggedMessage(WPARAM wParam) {
-        return (wParam == kMessageTag);
-    }
+    static constexpr const struct {
+        const WPARAM wParam = 0xF1C9ADD4;
+        const LPARAM lParam = 0xAFB6F4C6;
+    } kMessageTag;
 
     static inline quint64 getKeyState() {
         quint64 result = 0;
@@ -708,7 +876,7 @@
                 // wParam is always ignored in mouse leave messages, but here we
                 // give them a special tag to be able to distinguish which messages
                 // are sent by ourselves.
-                return kMessageTag;
+                return kMessageTag.wParam;
             }
             const quint64 keyState = getKeyState();
             if ((myMsg >= WM_NCXBUTTONDOWN) && (myMsg <= WM_NCXBUTTONDBLCLK)) {
@@ -786,6 +954,7 @@
                 SEND_MESSAGE(hWnd, WM_MOUSELEAVE, wParamNew, lParamNew);
                 break;
             default:
+                // unreachable
                 break;
         }
 
@@ -808,7 +977,7 @@
                                                LPARAM lParam, LRESULT *result) {
         switch (message) {
             case WM_MOUSELEAVE: {
-                if (!isTaggedMessage(wParam)) {
+                if (wParam == kMessageTag.wParam) {
                     // Qt will call TrackMouseEvent() to get the WM_MOUSELEAVE message when it
                     // receives WM_MOUSEMOVE messages, and since we are converting every
                     // WM_NCMOUSEMOVE message to WM_MOUSEMOVE message and send it back to the window
@@ -820,9 +989,9 @@
                     DWORD dwScreenPos = ::GetMessagePos();
                     POINT screenPoint{GET_X_LPARAM(dwScreenPos), GET_Y_LPARAM(dwScreenPos)};
                     ::ScreenToClient(hWnd, &screenPoint);
-                    QPoint qtScenePos =
-                        fromNativeLocalPosition(m_windowHandle, {screenPoint.x, screenPoint.y});
-                    auto dummy = CoreWindowAgent::Unknown;
+                    QPoint qtScenePos = QHighDpi::fromNativeLocalPosition(point2qpoint(screenPoint),
+                                                                          m_windowHandle);
+                    auto dummy = WindowAgentBase::Unknown;
                     if (isInSystemButtons(qtScenePos, &dummy)) {
                         // We must record whether the last WM_MOUSELEAVE was filtered, because if
                         // Qt does not receive this message it will not call TrackMouseEvent()
@@ -869,7 +1038,7 @@
                 const WindowPart currentWindowPart = lastHitTestResult;
                 if (message == WM_NCMOUSEMOVE) {
                     if (currentWindowPart != WindowPart::ChromeButton) {
-                        std::ignore = m_delegate->resetQtGrabbedControl();
+                        m_delegate->resetQtGrabbedControl();
                         if (mouseLeaveBlocked) {
                             emulateClientAreaMessage(hWnd, message, wParam, lParam,
                                                      WM_NCMOUSELEAVE);
@@ -931,7 +1100,7 @@
                         // 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.
-                        std::ignore = m_delegate->resetQtGrabbedControl();
+                        m_delegate->resetQtGrabbedControl();
                     }
                 }
                 break;
@@ -1053,14 +1222,14 @@
                 auto clientWidth = RECT_WIDTH(clientRect);
                 auto clientHeight = RECT_HEIGHT(clientRect);
 
-                QPoint qtScenePos = fromNativeLocalPosition(
-                    m_windowHandle, QPoint(nativeLocalPos.x, nativeLocalPos.y));
+                QPoint qtScenePos =
+                    QHighDpi::fromNativeLocalPosition(point2qpoint(nativeLocalPos), m_windowHandle);
 
                 bool isFixedSize = m_delegate->isHostSizeFixed(m_host);
                 bool isTitleBar = isInTitleBarDraggableArea(qtScenePos);
                 bool dontOverrideCursor = false; // ### TODO
 
-                CoreWindowAgent::SystemButton sysButtonType = CoreWindowAgent::Unknown;
+                WindowAgentBase::SystemButton sysButtonType = WindowAgentBase::Unknown;
                 if (!isFixedSize && isInSystemButtons(qtScenePos, &sysButtonType)) {
                     // Firstly, we set the hit test result to a default value to be able to detect
                     // whether we have changed it or not afterwards.
@@ -1070,22 +1239,31 @@
                     // this is also the normal behavior of a native Win32 window (but only when the
                     // window is not maximized/fullscreen/minimized, of course).
                     if (isWindowNoState(hWnd)) {
-                        static constexpr const int kBorderSize = 2;
+                        static constexpr const auto kBorderSize = quint8{2};
                         bool isTop = (nativeLocalPos.y <= kBorderSize);
+                        bool isLeft = nativeLocalPos.x <= kBorderSize;
                         bool isRight = (nativeLocalPos.x >= (clientWidth - kBorderSize));
-                        if (isTop || isRight) {
+                        if (isTop || isLeft || isRight) {
                             if (dontOverrideCursor) {
                                 // The user doesn't want the window to be resized, so we tell
                                 // Windows we are in the client area so that the controls beneath
                                 // the mouse cursor can still be hovered or clicked.
                                 *result = (isTitleBar ? HTCAPTION : HTCLIENT);
                             } else {
-                                if (isTop && isRight) {
-                                    *result = HTTOPRIGHT;
-                                } else if (isTop) {
-                                    *result = HTTOP;
+                                if (isTop) {
+                                    if (isLeft) {
+                                        *result = HTTOPLEFT;
+                                    } else if (isRight) {
+                                        *result = HTTOPRIGHT;
+                                    } else {
+                                        *result = HTTOP;
+                                    }
                                 } else {
-                                    *result = HTRIGHT;
+                                    if (isLeft) {
+                                        *result = HTLEFT;
+                                    } else {
+                                        *result = HTRIGHT;
+                                    }
                                 }
                             }
                         }
@@ -1095,23 +1273,24 @@
                         // exact role of our button. The Snap Layout feature introduced in Windows
                         // 11 won't work without this.
                         switch (sysButtonType) {
-                            case CoreWindowAgent::WindowIcon:
+                            case WindowAgentBase::WindowIcon:
                                 *result = HTSYSMENU;
                                 break;
-                            case CoreWindowAgent::Help:
+                            case WindowAgentBase::Help:
                                 *result = HTHELP;
                                 break;
-                            case CoreWindowAgent::Minimize:
+                            case WindowAgentBase::Minimize:
                                 *result = HTREDUCE;
                                 break;
-                            case CoreWindowAgent::Maximize:
+                            case WindowAgentBase::Maximize:
                                 *result = HTZOOM;
                                 break;
-                            case CoreWindowAgent::Close:
+                            case WindowAgentBase::Close:
                                 *result = HTCLOSE;
                                 break;
                             default:
-                                break; // unreachable
+                                // unreachable
+                                break;
                         }
                     }
                     if (*result == HTNOWHERE) {
@@ -1185,9 +1364,9 @@
                         const bool isBottom = (nativeLocalPos.y >= (clientHeight - frameSize));
                         // Make the border a little wider to let the user easy to resize on corners.
                         const auto scaleFactor = ((isTop || isBottom) ? qreal(2) : qreal(1));
-                        const int scaledFrameSizeX = std::round(qreal(frameSize) * scaleFactor);
-                        const bool isLeft = (nativeLocalPos.x < scaledFrameSizeX);
-                        const bool isRight = (nativeLocalPos.x >= (clientWidth - scaledFrameSizeX));
+                        const int scaledFrameSize = std::round(qreal(frameSize) * scaleFactor);
+                        const bool isLeft = (nativeLocalPos.x < scaledFrameSize);
+                        const bool isRight = (nativeLocalPos.x >= (clientWidth - scaledFrameSize));
                         if (dontOverrideCursor && (isTop || isBottom || isLeft || isRight)) {
                             // Return HTCLIENT instead of HTBORDER here, because the mouse is
                             // inside the window now, return HTCLIENT to let the controls
@@ -1305,6 +1484,7 @@
     bool Win32WindowContext::nonClientCalcSizeHandler(HWND hWnd, UINT message, WPARAM wParam,
                                                       LPARAM lParam, LRESULT *result) {
         Q_UNUSED(message)
+        Q_UNUSED(this)
 
         // Windows鏄牴鎹繖涓秷鎭殑杩斿洖鍊兼潵璁剧疆绐楀彛鐨勫鎴峰尯锛堢獥鍙d腑鐪熸鏄剧ず鐨勫唴瀹癸級
         // 鍜岄潪瀹㈡埛鍖猴紙鏍囬鏍忋�佺獥鍙h竟妗嗐�佽彍鍗曟爮鍜岀姸鎬佹爮绛塛indows绯荤粺鑷鎻愪緵鐨勯儴鍒�
@@ -1529,4 +1709,92 @@
         return true;
     }
 
+    bool Win32WindowContext::systemMenuHandler(HWND hWnd, UINT message, WPARAM wParam,
+                                               LPARAM lParam, LRESULT *result) {
+        const auto getNativePosFromMouse = [lParam]() -> POINT {
+            return {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
+        };
+        const auto getNativeGlobalPosFromKeyboard = [hWnd]() -> POINT {
+            const bool maxOrFull = IsMaximized(hWnd) || isFullScreen(hWnd);
+            const quint32 frameSize = getResizeBorderThickness(hWnd);
+            const quint32 horizontalOffset = ((maxOrFull || !isWin10OrGreater()) ? 0 : frameSize);
+            const auto verticalOffset = [hWnd, maxOrFull, frameSize]() -> quint32 {
+                const quint32 titleBarHeight = getTitleBarHeight(hWnd);
+                if (!isWin10OrGreater()) {
+                    return titleBarHeight;
+                }
+                if (isWin11OrGreater()) {
+                    if (maxOrFull) {
+                        return (titleBarHeight + frameSize);
+                    }
+                    return titleBarHeight;
+                }
+                if (maxOrFull) {
+                    return titleBarHeight;
+                }
+                return titleBarHeight - frameSize;
+            }();
+            RECT windowPos{};
+            ::GetWindowRect(hWnd, &windowPos);
+            return {static_cast<LONG>(windowPos.left + horizontalOffset),
+                    static_cast<LONG>(windowPos.top + verticalOffset)};
+        };
+        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)) {
+                    shouldShowSystemMenu = true;
+                    nativeGlobalPos = nativeLocalPos;
+                    ::ClientToScreen(hWnd, &nativeGlobalPos);
+                }
+                break;
+            }
+            case WM_NCRBUTTONUP: {
+                if (wParam == HTCAPTION) {
+                    shouldShowSystemMenu = true;
+                    nativeGlobalPos = getNativePosFromMouse();
+                }
+                break;
+            }
+            case WM_SYSCOMMAND: {
+                const WPARAM filteredWParam = (wParam & 0xFFF0);
+                if ((filteredWParam == SC_KEYMENU) && (lParam == VK_SPACE)) {
+                    shouldShowSystemMenu = true;
+                    broughtByKeyboard = true;
+                    nativeGlobalPos = getNativeGlobalPosFromKeyboard();
+                }
+                break;
+            }
+            case WM_KEYDOWN:
+            case WM_SYSKEYDOWN: {
+                const bool altPressed = ((wParam == VK_MENU) || (::GetKeyState(VK_MENU) < 0));
+                const bool spacePressed = ((wParam == VK_SPACE) || (::GetKeyState(VK_SPACE) < 0));
+                if (altPressed && spacePressed) {
+                    shouldShowSystemMenu = true;
+                    broughtByKeyboard = true;
+                    nativeGlobalPos = getNativeGlobalPosFromKeyboard();
+                }
+                break;
+            }
+            default:
+                break;
+        }
+        if (shouldShowSystemMenu) {
+            showSystemMenu2(hWnd, nativeGlobalPos, broughtByKeyboard,
+                            m_delegate->isHostSizeFixed(m_host));
+            // 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
+            // entering Qt's own handling logic.
+            *result = FALSE;
+            return true;
+        }
+        return false;
+    }
+
 }

--
Gitblit v1.9.1