From 5078f898257a53295167f22c67005ad1b30d4bf8 Mon Sep 17 00:00:00 2001
From: Sine Striker <trueful@163.com>
Date: 周二, 12 12月 2023 02:53:57 +0800
Subject: [PATCH] Tested MinGW 13.2.0

---
 src/core/contexts/win32windowcontext.cpp |  230 ++++++++++++++++++++++++++++++++++++++++++++++-----------
 1 files changed, 186 insertions(+), 44 deletions(-)

diff --git a/src/core/contexts/win32windowcontext.cpp b/src/core/contexts/win32windowcontext.cpp
index ccc318d..297c39c 100644
--- a/src/core/contexts/win32windowcontext.cpp
+++ b/src/core/contexts/win32windowcontext.cpp
@@ -27,6 +27,7 @@
 #include <timeapi.h>
 
 #include "nativeeventfilter.h"
+#include "qwkglobal_p.h"
 
 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
 Q_DECLARE_METATYPE(QMargins)
@@ -34,16 +35,42 @@
 
 namespace QWK {
 
-    // The thickness of an auto-hide taskbar in pixels.
-    static constexpr const auto kAutoHideTaskBarThickness = quint8{2};
+    enum _DWMWINDOWATTRIBUTE {
+        // [set] BOOL, Allows the use of host backdrop brushes for the window.
+        _DWMWA_USE_HOSTBACKDROPBRUSH = 17,
 
-    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
+        // 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 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 *>;
@@ -63,23 +90,30 @@
     } 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 DYNAMIC_API_DECLARE(NAME) decltype(&::NAME) p##NAME =
-// DefaultFunc<decltype(&::NAME)>::func
+        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);
@@ -89,6 +123,7 @@
 
 #undef DYNAMIC_API_DECLARE
 
+    private:
         DynamicApis() {
 #define DYNAMIC_API_RESOLVE(DLL, NAME)                                                             \
     p##NAME = reinterpret_cast<decltype(p##NAME)>(DLL.resolve(#NAME))
@@ -104,6 +139,7 @@
             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);
@@ -115,12 +151,6 @@
 
         ~DynamicApis() = default;
 
-        static const DynamicApis &instance() {
-            static const DynamicApis inst{};
-            return inst;
-        }
-
-    private:
         Q_DISABLE_COPY_MOVE(DynamicApis)
     };
 
@@ -265,6 +295,21 @@
 #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);
@@ -306,9 +351,22 @@
         } 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 getWindowFrameBorderThickness(HWND hwnd) {
+        UINT result = 0;
+        const DynamicApis &apis = DynamicApis::instance();
+        if (SUCCEEDED(apis.pDwmGetWindowAttribute(hwnd, _DWMWA_VISIBLE_FRAME_BORDER_THICKNESS,
+                                                  &result, sizeof(result)))) {
+            return result;
+        } else {
+            const quint32 dpi = getDpiForWindow(hwnd);
+            result = quint32(std::round(qreal(1) * qreal(dpi) / qreal(USER_DEFAULT_SCREEN_DPI)));
+            return result;
         }
     }
 
@@ -338,11 +396,11 @@
 
     static inline 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};
             }
         }();
@@ -732,32 +790,96 @@
     }
 
     QString Win32WindowContext::key() const {
-        return "win32";
+        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 = *reinterpret_cast<const QPoint *>(data);
-                auto winId = m_windowHandle->winId();
-                auto hWnd = reinterpret_cast<HWND>(winId);
+                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 NeedsDrawBordersHook: {
-                auto &result = *reinterpret_cast<bool *>(data);
+                auto &result = *static_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
+
+            case BorderThicknessHook: {
+                auto args = static_cast<void **>(data);
+                const bool requireNative = *static_cast<const bool *>(args[0]);
+                quint32 &thickness = *static_cast<quint32 *>(args[1]);
+                const auto hwnd = reinterpret_cast<HWND>(m_windowHandle->winId());
+                const auto nativeThickness = getWindowFrameBorderThickness(hwnd);
+                thickness = requireNative
+                                ? nativeThickness
+                                : QHighDpi::fromNativePixels(nativeThickness, m_windowHandle);
                 return;
             }
+
+            case BorderColorsHook: {
+                auto arr = *reinterpret_cast<QList<QColor> *>(data);
+                arr.clear();
+                arr.push_back(kWindowsColorSet.activeLight);
+                arr.push_back(kWindowsColorSet.activeDark);
+                arr.push_back(kWindowsColorSet.inactiveLight);
+                arr.push_back(kWindowsColorSet.inactiveDark);
+                return;
+            }
+
+            case DrawBordersHook: {
+                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;
+                const auto borderThickness = int(QHighDpi::fromNativePixels(
+                    getWindowFrameBorderThickness(hwnd), m_windowHandle));
+                pen.setWidth(borderThickness * 2);
+                const bool active = m_delegate->isWindowActive(m_host);
+                const bool dark = isDarkThemeActive() && isDarkWindowFrameEnabled(hwnd);
+
+                if (active) {
+                    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{rect.width(), 0}
+                });
+                painter.restore();
+                return;
+            }
+
             default: {
                 // unreachable
                 break;
@@ -826,6 +948,7 @@
             return true;
         }
 
+        // Whether to show system menu
         if (systemMenuHandler(hWnd, message, wParam, lParam, result)) {
             return true;
         }
@@ -833,7 +956,7 @@
         return false; // Not handled
     }
 
-    static constexpr const struct {
+    QWK_USED static constexpr const struct {
         const WPARAM wParam = 0xF1C9ADD4;
         const LPARAM lParam = 0xAFB6F4C6;
     } kMessageTag;
@@ -977,7 +1100,7 @@
                                                LPARAM lParam, LRESULT *result) {
         switch (message) {
             case WM_MOUSELEAVE: {
-                if (wParam == kMessageTag.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
@@ -1127,6 +1250,7 @@
                 }
                 break;
             }
+
             case WM_NCHITTEST: {
                 // 鍘熺敓Win32绐楀彛鍙湁椤惰竟鏄湪绐楀彛鍐呴儴resize鐨勶紝鍏朵綑涓夎竟閮芥槸鍦ㄧ獥鍙�
                 // 澶栭儴杩涜resize鐨勶紝鍏跺師鐞嗘槸锛學S_THICKFRAME杩欎釜绐楀彛鏍峰紡浼氬湪绐�
@@ -1239,7 +1363,7 @@
                     // 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 auto kBorderSize = quint8{2};
+                        static constexpr const quint8 kBorderSize = 2;
                         bool isTop = (nativeLocalPos.y <= kBorderSize);
                         bool isLeft = nativeLocalPos.x <= kBorderSize;
                         bool isRight = (nativeLocalPos.x >= (clientWidth - kBorderSize));
@@ -1415,9 +1539,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:

--
Gitblit v1.9.1