From e18723d66cf470720c29a9d2848150ea1cbe5450 Mon Sep 17 00:00:00 2001
From: Sine Striker <trueful@163.com>
Date: 周四, 14 12月 2023 04:49:35 +0800
Subject: [PATCH] Add example resources

---
 src/core/contexts/win32windowcontext.cpp |  606 ++++++++++++++++++++++++++++++++++++------------------
 1 files changed, 403 insertions(+), 203 deletions(-)

diff --git a/src/core/contexts/win32windowcontext.cpp b/src/core/contexts/win32windowcontext.cpp
index c475a62..a2340ac 100644
--- a/src/core/contexts/win32windowcontext.cpp
+++ b/src/core/contexts/win32windowcontext.cpp
@@ -1,13 +1,16 @@
 #include "win32windowcontext_p.h"
-#include "qwkcoreglobal_p.h"
 
 #include <optional>
 
 #include <QtCore/QHash>
-#include <QtCore/QAbstractNativeEventFilter>
 #include <QtCore/QScopeGuard>
+#include <QtCore/QTimer>
 #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)
@@ -23,14 +26,51 @@
 #include <shellscalingapi.h>
 #include <dwmapi.h>
 #include <timeapi.h>
-#include <versionhelpers.h>
 
+#include "qwkglobal_p.h"
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
 Q_DECLARE_METATYPE(QMargins)
+#endif
 
 namespace QWK {
 
+    enum _DWMWINDOWATTRIBUTE {
+        // [set] BOOL, Allows the use of host backdrop brushes for the window.
+        _DWMWA_USE_HOSTBACKDROPBRUSH = 17,
+
+        // Undocumented, the same with DWMWA_USE_IMMERSIVE_DARK_MODE, but available on systems
+        // before Win10 20H1.
+        _DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 = 19,
+
+        // [set] BOOL, Allows a window to either use the accent color, or dark, according to the
+        // user Color Mode preferences.
+        _DWMWA_USE_IMMERSIVE_DARK_MODE = 20,
+
+        // [set] WINDOW_CORNER_PREFERENCE, Controls the policy that rounds top-level window corners
+        _DWMWA_WINDOW_CORNER_PREFERENCE = 33,
+
+        // [get] UINT, width of the visible border around a thick frame window
+        _DWMWA_VISIBLE_FRAME_BORDER_THICKNESS = 37,
+
+        // [get, set] SYSTEMBACKDROP_TYPE, Controls the system-drawn backdrop material of a window,
+        // including behind the non-client area.
+        _DWMWA_SYSTEMBACKDROP_TYPE = 38,
+
+        // Undocumented, use this value to enable Mica material on Win11 21H2. You should use
+        // DWMWA_SYSTEMBACKDROP_TYPE instead on Win11 22H2 and newer.
+        _DWMWA_MICA_EFFECT = 1029
+    };
+
     // The thickness of an auto-hide taskbar in pixels.
-    static constexpr const auto kAutoHideTaskBarThickness = quint8{2};
+    static constexpr const quint8 kAutoHideTaskBarThickness = 2;
+
+    QWK_USED static constexpr const struct {
+        const uint32_t activeLight = MAKE_RGBA_COLOR(110, 110, 110, 255);   // #6E6E6E
+        const uint32_t activeDark = MAKE_RGBA_COLOR(51, 51, 51, 255);       // #333333
+        const uint32_t inactiveLight = MAKE_RGBA_COLOR(167, 167, 167, 255); // #A7A7A7
+        const uint32_t inactiveDark = MAKE_RGBA_COLOR(61, 61, 62, 255);     // #3D3D3E
+    } kWindowsColorSet;
 
     // hWnd -> context
     using WndProcHash = QHash<HWND, Win32WindowContext *>;
@@ -43,73 +83,74 @@
     // ### FIXME: Tell the user to call in the documentation, instead of automatically
     // calling it directly.
     // ### FIXME FIXME FIXME
-    static struct QWK_Hook {
+    static const struct QWK_Hook {
         QWK_Hook() {
             qApp->setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
         }
     } g_hook{};
 
     struct DynamicApis {
-//        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 DWM_API_DECLARE(NAME) decltype(&::NAME) p##NAME = DefaultFunc<decltype(&::NAME)>::func
-#define DWM_API_DECLARE(NAME) decltype(&::NAME) p##NAME = nullptr
-
-        DWM_API_DECLARE(DwmFlush);
-        DWM_API_DECLARE(DwmIsCompositionEnabled);
-        DWM_API_DECLARE(DwmGetCompositionTimingInfo);
-        DWM_API_DECLARE(GetDpiForWindow);
-        DWM_API_DECLARE(GetSystemMetricsForDpi);
-        DWM_API_DECLARE(GetDpiForMonitor);
-        DWM_API_DECLARE(timeGetDevCaps);
-        DWM_API_DECLARE(timeBeginPeriod);
-        DWM_API_DECLARE(timeEndPeriod);
-
-#undef DWM_API_DECLARE
-
-        DynamicApis() {
-            QSystemLibrary user32(QStringLiteral("user32"));
-            pGetDpiForWindow =
-                reinterpret_cast<decltype(pGetDpiForWindow)>(user32.resolve("GetDpiForWindow"));
-            pGetSystemMetricsForDpi = reinterpret_cast<decltype(pGetSystemMetricsForDpi)>(
-                user32.resolve("GetSystemMetricsForDpi"));
-
-            QSystemLibrary shcore(QStringLiteral("shcore"));
-            pGetDpiForMonitor =
-                reinterpret_cast<decltype(pGetDpiForMonitor)>(shcore.resolve("GetDpiForMonitor"));
-
-            QSystemLibrary dwmapi(QStringLiteral("dwmapi"));
-            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 winmm(QStringLiteral("winmm"));
-            ptimeGetDevCaps =
-                reinterpret_cast<decltype(ptimeGetDevCaps)>(winmm.resolve("timeGetDevCaps"));
-            ptimeBeginPeriod =
-                reinterpret_cast<decltype(ptimeBeginPeriod)>(winmm.resolve("timeBeginPeriod"));
-            ptimeEndPeriod =
-                reinterpret_cast<decltype(ptimeEndPeriod)>(winmm.resolve("timeEndPeriod"));
-        }
-
-        ~DynamicApis() = default;
-
         static const DynamicApis &instance() {
             static const DynamicApis inst{};
             return inst;
         }
 
+        //        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(DwmGetWindowAttribute);
+        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
+
     private:
+        DynamicApis() {
+#define DYNAMIC_API_RESOLVE(DLL, NAME)                                                             \
+    p##NAME = reinterpret_cast<decltype(p##NAME)>(DLL.resolve(#NAME))
+
+            QSystemLibrary user32(QStringLiteral("user32"));
+            DYNAMIC_API_RESOLVE(user32, GetDpiForWindow);
+            DYNAMIC_API_RESOLVE(user32, GetSystemMetricsForDpi);
+
+            QSystemLibrary shcore(QStringLiteral("shcore"));
+            DYNAMIC_API_RESOLVE(shcore, GetDpiForMonitor);
+
+            QSystemLibrary dwmapi(QStringLiteral("dwmapi"));
+            DYNAMIC_API_RESOLVE(dwmapi, DwmFlush);
+            DYNAMIC_API_RESOLVE(dwmapi, DwmIsCompositionEnabled);
+            DYNAMIC_API_RESOLVE(dwmapi, DwmGetCompositionTimingInfo);
+            DYNAMIC_API_RESOLVE(dwmapi, DwmGetWindowAttribute);
+
+            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;
+
         Q_DISABLE_COPY_MOVE(DynamicApis)
     };
 
@@ -194,22 +235,22 @@
     }
 
     static inline bool isWin8OrGreater() {
-        static const bool result = ::IsWindows8OrGreater();
+        static const bool result = IsWindows8OrGreater_Real();
         return result;
     }
 
     static inline bool isWin8Point1OrGreater() {
-        static const bool result = ::IsWindows8Point1OrGreater();
+        static const bool result = IsWindows8Point1OrGreater_Real();
         return result;
     }
 
     static inline bool isWin10OrGreater() {
-        static const bool result = ::IsWindows10OrGreater();
+        static const bool result = IsWindows10OrGreater_Real();
         return result;
     }
 
     static inline bool isWin11OrGreater() {
-        static const bool result = ::IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WIN10), LOBYTE(_WIN32_WINNT_WIN10), 22000);
+        static const bool result = IsWindows11OrGreater_Real();
         return result;
     }
 
@@ -223,6 +264,72 @@
         }
         BOOL enabled = FALSE;
         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 bool isDarkWindowFrameEnabled(HWND hwnd) {
+        BOOL enabled = FALSE;
+        const DynamicApis &apis = DynamicApis::instance();
+        if (SUCCEEDED(apis.pDwmGetWindowAttribute(hwnd, _DWMWA_USE_IMMERSIVE_DARK_MODE, &enabled,
+                                                  sizeof(enabled)))) {
+            return enabled;
+        } else if (SUCCEEDED(apis.pDwmGetWindowAttribute(hwnd,
+                                                         _DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1,
+                                                         &enabled, sizeof(enabled)))) {
+            return enabled;
+        } else {
+            return false;
+        }
+    }
+
+    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) {
@@ -244,43 +351,49 @@
         } else { // Win2K
             HDC hdc = ::GetDC(nullptr);
             const int dpiX = ::GetDeviceCaps(hdc, LOGPIXELSX);
-            const int dpiY = ::GetDeviceCaps(hdc, LOGPIXELSY);
+            // const int dpiY = ::GetDeviceCaps(hdc, LOGPIXELSY);
             ::ReleaseDC(nullptr, hdc);
             return quint32(dpiX);
         }
     }
 
-    static inline quint32 getResizeBorderThickness(HWND hwnd) {
+    static inline quint32 getSystemMetricsForDpi(int index, quint32 dpi) {
         const DynamicApis &apis = DynamicApis::instance();
         if (apis.pGetSystemMetricsForDpi) {
-            const quint32 dpi = getDpiForWindow(hwnd);
-            return apis.pGetSystemMetricsForDpi(SM_CXSIZEFRAME, dpi) +
-                   apis.pGetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi);
-        } else {
-            return ::GetSystemMetrics(SM_CXSIZEFRAME) + ::GetSystemMetrics(SM_CXPADDEDBORDER);
+            return ::GetSystemMetricsForDpi(index, dpi);
         }
+        return ::GetSystemMetrics(index);
+    }
+
+    static inline quint32 getWindowFrameBorderThickness(HWND hwnd) {
+        const DynamicApis &apis = DynamicApis::instance();
+        if (UINT result = 0; SUCCEEDED(apis.pDwmGetWindowAttribute(
+                hwnd, _DWMWA_VISIBLE_FRAME_BORDER_THICKNESS, &result, sizeof(result)))) {
+            return result;
+        }
+        return getSystemMetricsForDpi(SM_CXBORDER, getDpiForWindow(hwnd));
+    }
+
+    static inline quint32 getResizeBorderThickness(HWND hwnd) {
+        const quint32 dpi = getDpiForWindow(hwnd);
+        return getSystemMetricsForDpi(SM_CXSIZEFRAME, dpi) +
+               getSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi);
     }
 
     static inline quint32 getTitleBarHeight(HWND hwnd) {
-        const auto captionHeight = [hwnd]() -> int {
-            const DynamicApis &apis = DynamicApis::instance();
-            if (apis.pGetSystemMetricsForDpi) {
-                const quint32 dpi = getDpiForWindow(hwnd);
-                return apis.pGetSystemMetricsForDpi(SM_CYCAPTION, dpi);
-            } else {
-                return ::GetSystemMetrics(SM_CYCAPTION);
-            }
-        }();
-        return captionHeight + getResizeBorderThickness(hwnd);
+        const quint32 dpi = getDpiForWindow(hwnd);
+        return getSystemMetricsForDpi(SM_CYCAPTION, dpi) +
+               getSystemMetricsForDpi(SM_CXSIZEFRAME, dpi) +
+               getSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi);
     }
 
-    static inline void updateInternalWindowFrameMargins(HWND hwnd, QWindow *window) {
+    static void updateInternalWindowFrameMargins(HWND hwnd, QWindow *window) {
         const auto margins = [hwnd]() -> QMargins {
-            const int titleBarHeight = getTitleBarHeight(hwnd);
+            const auto titleBarHeight = int(getTitleBarHeight(hwnd));
             if (isWin10OrGreater()) {
                 return {0, -titleBarHeight, 0, 0};
             } else {
-                const int frameSize = getResizeBorderThickness(hwnd);
+                const auto frameSize = int(getResizeBorderThickness(hwnd));
                 return {-frameSize, -titleBarHeight, -frameSize, -frameSize};
             }
         }();
@@ -340,7 +453,7 @@
 #endif
     }
 
-    static inline void syncPaintEventWithDwm() {
+    static void syncPaintEventWithDwm() {
         // No need to sync with DWM if DWM composition is disabled.
         if (!isDwmCompositionEnabled()) {
             return;
@@ -385,9 +498,9 @@
         apis.ptimeEndPeriod(ms_granularity);
     }
 
-    static inline void showSystemMenu2(HWND hWnd, const POINT &pos, const bool selectFirstEntry)
-    {
-        const HMENU hMenu = ::GetSystemMenu(hWnd, FALSE);
+    static void showSystemMenu2(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
@@ -395,97 +508,53 @@
             return;
         }
 
-        // Tweak the menu items according to the current window status and user settings.
-        const bool disableClose = /*data->callbacks->getProperty(kSysMenuDisableCloseVar, false).toBool()*/false;
-        const bool disableRestore = /*data->callbacks->getProperty(kSysMenuDisableRestoreVar, false).toBool()*/false;
-        const bool disableMinimize = /*data->callbacks->getProperty(kSysMenuDisableMinimizeVar, false).toBool()*/false;
-        const bool disableMaximize = /*data->callbacks->getProperty(kSysMenuDisableMaximizeVar, false).toBool()*/false;
-        const bool disableSize = /*data->callbacks->getProperty(kSysMenuDisableSizeVar, false).toBool()*/false;
-        const bool disableMove = /*data->callbacks->getProperty(kSysMenuDisableMoveVar, false).toBool()*/false;
-        const bool removeClose = /*data->callbacks->getProperty(kSysMenuRemoveCloseVar, false).toBool()*/false;
-        const bool removeSeparator = /*data->callbacks->getProperty(kSysMenuRemoveSeparatorVar, false).toBool()*/false;
-        const bool removeRestore = /*data->callbacks->getProperty(kSysMenuRemoveRestoreVar, false).toBool()*/false;
-        const bool removeMinimize = /*data->callbacks->getProperty(kSysMenuRemoveMinimizeVar, false).toBool()*/false;
-        const bool removeMaximize = /*data->callbacks->getProperty(kSysMenuRemoveMaximizeVar, false).toBool()*/false;
-        const bool removeSize = /*data->callbacks->getProperty(kSysMenuRemoveSizeVar, false).toBool()*/false;
-        const bool removeMove = /*data->callbacks->getProperty(kSysMenuRemoveMoveVar, false).toBool()*/false;
         const bool maxOrFull = IsMaximized(hWnd) || isFullScreen(hWnd);
-        const bool fixedSize = /*data->callbacks->isWindowFixedSize()*/false;
-        if (removeClose) {
-            ::DeleteMenu(hMenu, SC_CLOSE, MF_BYCOMMAND);
-        } else {
-            ::EnableMenuItem(hMenu, SC_CLOSE, (MF_BYCOMMAND | (disableClose ? MFS_DISABLED : MFS_ENABLED)));
-        }
-        if (removeSeparator) {
-            // Looks like we must use 0 for the second parameter here, otherwise we can't remove the separator.
-            ::DeleteMenu(hMenu, 0, MFT_SEPARATOR);
-        }
-        if (removeMaximize) {
-            ::DeleteMenu(hMenu, SC_MAXIMIZE, MF_BYCOMMAND);
-        } else {
-            ::EnableMenuItem(hMenu, SC_MAXIMIZE, (MF_BYCOMMAND | ((maxOrFull || fixedSize || disableMaximize) ? MFS_DISABLED : MFS_ENABLED)));
-        }
-        if (removeRestore) {
-            ::DeleteMenu(hMenu, SC_RESTORE, MF_BYCOMMAND);
-        } else {
-            ::EnableMenuItem(hMenu, SC_RESTORE, (MF_BYCOMMAND | ((maxOrFull && !fixedSize && !disableRestore) ? 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)));
-        }
-        if (removeMinimize) {
-            ::DeleteMenu(hMenu, SC_MINIMIZE, MF_BYCOMMAND);
-        } else {
-            ::EnableMenuItem(hMenu, SC_MINIMIZE, (MF_BYCOMMAND | (disableMinimize ? MFS_DISABLED : MFS_ENABLED)));
-        }
-        if (removeSize) {
-            ::DeleteMenu(hMenu, SC_SIZE, MF_BYCOMMAND);
-        } else {
-            ::EnableMenuItem(hMenu, SC_SIZE, (MF_BYCOMMAND | ((maxOrFull || fixedSize || disableSize || disableMinimize || disableMaximize) ? MFS_DISABLED : MFS_ENABLED)));
-        }
-        if (removeMove) {
-            ::DeleteMenu(hMenu, SC_MOVE, MF_BYCOMMAND);
-        } else {
-            ::EnableMenuItem(hMenu, SC_MOVE, (MF_BYCOMMAND | ((maxOrFull || disableMove) ? MFS_DISABLED : MFS_ENABLED)));
-        }
+        ::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.
-        std::optional<UINT> defaultItemId = std::nullopt;
+        UINT defaultItemId = UINT_MAX;
         if (isWin11OrGreater()) {
             if (maxOrFull) {
-                if (!removeRestore) {
-                    defaultItemId = SC_RESTORE;
-                }
+                defaultItemId = SC_RESTORE;
             } else {
-                if (!removeMaximize) {
-                    defaultItemId = SC_MAXIMIZE;
-                }
+                defaultItemId = SC_MAXIMIZE;
             }
         }
-        if (!(defaultItemId.has_value() || removeClose)) {
+        if (defaultItemId == UINT_MAX) {
             defaultItemId = SC_CLOSE;
         }
-        ::SetMenuDefaultItem(hMenu, defaultItemId.value_or(UINT_MAX), FALSE);
-
-        ::DrawMenuBar(hWnd);
+        ::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);
+        const auto result = ::TrackPopupMenu(
+            hMenu,
+            (TPM_RETURNCMD | (QGuiApplication::isRightToLeft() ? TPM_RIGHTALIGN : TPM_LEFTALIGN)),
+            pos.x, pos.y, 0, hWnd, nullptr);
 
-        if (!removeRestore) {
-            // 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));
-        }
+        // 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.
@@ -520,7 +589,8 @@
             case HTBORDER:
                 return Win32WindowContext::FixedBorder;
             default:
-                break; // unreachable
+                // unreachable
+                break;
         }
         return Win32WindowContext::Outside;
     }
@@ -564,7 +634,7 @@
     // 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 AppNativeEventFilter {
     public:
         bool nativeEventFilter(const QByteArray &eventType, void *message,
                                QT_NATIVE_EVENT_RESULT_TYPE *result) override {
@@ -599,14 +669,12 @@
                 return;
             }
             instance = new WindowsNativeEventFilter();
-            installNativeEventFilter(instance);
         }
 
         static inline void uninstall() {
             if (!instance) {
                 return;
             }
-            removeNativeEventFilter(instance);
             delete instance;
             instance = nullptr;
         }
@@ -714,6 +782,92 @@
         }
     }
 
+    QString Win32WindowContext::key() const {
+        return QStringLiteral("win32");
+    }
+
+    void Win32WindowContext::virtual_hook(int id, void *data) {
+        switch (id) {
+            case CentralizeHook: {
+                const auto hwnd = reinterpret_cast<HWND>(m_windowHandle->winId());
+                moveToDesktopCenter(hwnd);
+                return;
+            }
+
+            case ShowSystemMenuHook: {
+                const auto &pos = *static_cast<const QPoint *>(data);
+                auto hWnd = reinterpret_cast<HWND>(m_windowHandle->winId());
+                showSystemMenu2(hWnd, qpoint2point(pos), false,
+                                m_delegate->isHostSizeFixed(m_host));
+                return;
+            }
+
+            case DefaultColorsHook: {
+                auto &map = *static_cast<QMap<QString, QColor> *>(data);
+                map.clear();
+                map.insert(QStringLiteral("activeLight"), kWindowsColorSet.activeLight);
+                map.insert(QStringLiteral("activeDark"), kWindowsColorSet.activeDark);
+                map.insert(QStringLiteral("inactiveLight"), kWindowsColorSet.inactiveLight);
+                map.insert(QStringLiteral("inactiveDark"), kWindowsColorSet.inactiveDark);
+                return;
+            }
+
+            case DrawWindows10BorderHook: {
+                auto args = static_cast<void **>(data);
+                auto &painter = *static_cast<QPainter *>(args[0]);
+                const auto &rect = *static_cast<const QRect *>(args[1]);
+                const auto &region = *static_cast<const QRegion *>(args[2]);
+                const auto hwnd = reinterpret_cast<HWND>(m_windowHandle->winId());
+
+                QPen pen;
+                pen.setWidth(getWindowFrameBorderThickness(hwnd) * 2);
+
+                const bool dark = isDarkThemeActive() && isDarkWindowFrameEnabled(hwnd);
+                if (m_delegate->isWindowActive(m_host)) {
+                    if (isWindowFrameBorderColorized()) {
+                        pen.setColor(getAccentColor());
+                    } else {
+                        static QColor frameBorderActiveColorLight(kWindowsColorSet.activeLight);
+                        static QColor frameBorderActiveColorDark(kWindowsColorSet.activeDark);
+                        pen.setColor(dark ? frameBorderActiveColorDark
+                                          : frameBorderActiveColorLight);
+                    }
+                } else {
+                    static QColor frameBorderInactiveColorLight(kWindowsColorSet.inactiveLight);
+                    static QColor frameBorderInactiveColorDark(kWindowsColorSet.inactiveDark);
+                    pen.setColor(dark ? frameBorderInactiveColorDark
+                                      : frameBorderInactiveColorLight);
+                }
+                painter.save();
+
+                // ### TODO: do we need to enable or disable it?
+                painter.setRenderHint(QPainter::Antialiasing);
+
+                painter.setPen(pen);
+                painter.drawLine(QLine{
+                    QPoint{0,                       0},
+                    QPoint{m_windowHandle->width(), 0}
+                });
+                painter.restore();
+                return;
+            }
+
+            default: {
+                // unreachable
+                break;
+            }
+        }
+        AbstractWindowContext::virtual_hook(id, data);
+    }
+
+    bool Win32WindowContext::needBorderPainter() const {
+        return isWin10OrGreater() && !isWin11OrGreater();
+    }
+
+    int Win32WindowContext::borderThickness() const {
+        return getWindowFrameBorderThickness(reinterpret_cast<HWND>(windowId));
+    }
+
     bool Win32WindowContext::setupHost() {
         // Install window hook
         auto winId = m_windowHandle->winId();
@@ -764,10 +918,6 @@
             return false;
         }
 
-        if (systemMenuHandler(hWnd, message, wParam, lParam, result)) {
-            return true;
-        }
-
         // Test snap layout
         if (snapLayoutHandler(hWnd, message, wParam, lParam, result)) {
             return true;
@@ -778,14 +928,31 @@
             return true;
         }
 
+        // Whether to show system menu
+        if (systemMenuHandler(hWnd, message, wParam, lParam, result)) {
+            return true;
+        }
+
+        // Forward to native event filter subscribers
+        if (!m_nativeEventFilters.isEmpty()) {
+            MSG msg;
+            msg.hwnd = hWnd;
+            msg.message = message;
+            msg.wParam = wParam;
+            msg.lParam = lParam;
+            QT_NATIVE_EVENT_RESULT_TYPE res = 0;
+            if (dispatch(QByteArrayLiteral("windows_generic_MSG"), &msg, &res)) {
+                *result = LRESULT(res);
+                return true;
+            }
+        }
         return false; // Not handled
     }
 
-    static constexpr const auto kMessageTag = WPARAM(0x97CCEA99);
-
-    static inline constexpr bool isTaggedMessage(WPARAM wParam) {
-        return (wParam == kMessageTag);
-    }
+    QWK_USED static constexpr const struct {
+        const WPARAM wParam = MAKEWPARAM(44500, 61897);
+        const LPARAM lParam = MAKELPARAM(62662, 44982); // Not used. Reserve for future use.
+    } kMessageTag;
 
     static inline quint64 getKeyState() {
         quint64 result = 0;
@@ -825,7 +992,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)) {
@@ -903,6 +1070,7 @@
                 SEND_MESSAGE(hWnd, WM_MOUSELEAVE, wParamNew, lParamNew);
                 break;
             default:
+                // unreachable
                 break;
         }
 
@@ -925,7 +1093,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
@@ -937,9 +1105,9 @@
                     DWORD dwScreenPos = ::GetMessagePos();
                     POINT screenPoint{GET_X_LPARAM(dwScreenPos), GET_Y_LPARAM(dwScreenPos)};
                     ::ScreenToClient(hWnd, &screenPoint);
-                    QPoint qtScenePos = QHighDpi::fromNativeLocalPosition(
-                        QPoint{screenPoint.x, screenPoint.y}, m_windowHandle);
-                    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()
@@ -1075,6 +1243,7 @@
                 }
                 break;
             }
+
             case WM_NCHITTEST: {
                 // 鍘熺敓Win32绐楀彛鍙湁椤惰竟鏄湪绐楀彛鍐呴儴resize鐨勶紝鍏朵綑涓夎竟閮芥槸鍦ㄧ獥鍙�
                 // 澶栭儴杩涜resize鐨勶紝鍏跺師鐞嗘槸锛學S_THICKFRAME杩欎釜绐楀彛鏍峰紡浼氬湪绐�
@@ -1170,14 +1339,14 @@
                 auto clientWidth = RECT_WIDTH(clientRect);
                 auto clientHeight = RECT_HEIGHT(clientRect);
 
-                QPoint qtScenePos = QHighDpi::fromNativeLocalPosition(
-                    QPoint(nativeLocalPos.x, nativeLocalPos.y), m_windowHandle);
+                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.
@@ -1187,22 +1356,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 quint8 kBorderSize = 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;
+                                    }
                                 }
                             }
                         }
@@ -1212,23 +1390,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) {
@@ -1302,9 +1481,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
@@ -1353,9 +1532,27 @@
                     return true;
                 }
             }
+
+            case WM_WINDOWPOSCHANGING: {
+                // ### FIXME: How does this problem happen and why is it solved?
+                // When toggling the "Show theme color in title bar and window border" setting in
+                // Windows Settings, or calling `DrawMenuBar()`, Windows sends a message of
+                // WM_WINDOWPOSCHANGING with flags 0x37. If we do not process this message,
+                // the client area as a whole will shift to the left, which looks very abnormal if
+                // we don't repaint it. This exception disappears if we add SWP_NOCOPYBITS flag.
+                // But I don't know what caused the problem, or why this would solve it.
+                const auto windowPos = reinterpret_cast<LPWINDOWPOS>(lParam);
+                if (windowPos->flags ==
+                    (SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED)) {
+                    windowPos->flags |= SWP_NOCOPYBITS;
+                }
+                break;
+            }
+
             default:
                 break;
         }
+
         if (!isWin10OrGreater()) {
             switch (message) {
                 case WM_NCUAHDRAWCAPTION:
@@ -1674,7 +1871,8 @@
             }();
             RECT windowPos{};
             ::GetWindowRect(hWnd, &windowPos);
-            return {static_cast<LONG>(windowPos.left + horizontalOffset), static_cast<LONG>(windowPos.top + verticalOffset)};
+            return {static_cast<LONG>(windowPos.left + horizontalOffset),
+                    static_cast<LONG>(windowPos.top + verticalOffset)};
         };
         bool shouldShowSystemMenu = false;
         bool broughtByKeyboard = false;
@@ -1682,7 +1880,8 @@
         switch (message) {
             case WM_RBUTTONUP: {
                 const POINT nativeLocalPos = getNativePosFromMouse();
-                const QPoint qtScenePos = QHighDpi::fromNativeLocalPosition(QPoint(nativeLocalPos.x, nativeLocalPos.y), m_windowHandle);
+                const QPoint qtScenePos =
+                    QHighDpi::fromNativeLocalPosition(point2qpoint(nativeLocalPos), m_windowHandle);
                 if (isInTitleBarDraggableArea(qtScenePos)) {
                     shouldShowSystemMenu = true;
                     nativeGlobalPos = nativeLocalPos;
@@ -1721,7 +1920,8 @@
                 break;
         }
         if (shouldShowSystemMenu) {
-            showSystemMenu2(hWnd, nativeGlobalPos, broughtByKeyboard);
+            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

--
Gitblit v1.9.1