Yuhang Zhao
2023-12-07 86403b4e99f901f687050ce9a91a8e539331cebb
src/core/contexts/win32windowcontext.cpp
@@ -1,20 +1,31 @@
#include "win32windowcontext_p.h"
#include "qwkcoreglobal_p.h"
#include <optional>
#include <QtCore/QHash>
#include <QtCore/QAbstractNativeEventFilter>
#include <QtCore/QCoreApplication>
#include <QtCore/QOperatingSystemVersion>
#include <QtCore/QScopeGuard>
#include <QtGui/QGuiApplication>
#include <QtCore/private/qsystemlibrary_p.h>
#include <QtGui/private/qhighdpiscaling_p.h>
#include "qwkcoreglobal_p.h"
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
#  include <QtGui/private/qguiapplication_p.h>
#endif
#include <QtGui/qpa/qplatformwindow.h>
#if QT_VERSION < QT_VERSION_CHECK(6, 2, 0)
#  include <QtGui/qpa/qplatformnativeinterface.h>
#else
#  include <QtGui/qpa/qplatformwindow_p.h>
#endif
#include <shellscalingapi.h>
#include <dwmapi.h>
#include <timeapi.h>
Q_DECLARE_METATYPE(QMargins)
namespace QWK {
@@ -26,12 +37,22 @@
    static WNDPROC g_qtWindowProc = nullptr; // Original Qt window proc function
    static struct QWK_Hook {
        QWK_Hook() {
            qApp->setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
        }
    } 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;
        DynamicApis() {
            QSystemLibrary user32(QStringLiteral("user32.dll"));
@@ -48,6 +69,12 @@
            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.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"));
        }
        ~DynamicApis() = default;
@@ -172,20 +199,12 @@
    }
    static inline void triggerFrameChange(HWND hwnd) {
        Q_ASSERT(hwnd);
        if (!hwnd) {
            return;
        }
        ::SetWindowPos(hwnd, nullptr, 0, 0, 0, 0,
                       SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER |
                           SWP_FRAMECHANGED);
    }
    static inline quint32 getDpiForWindow(HWND hwnd) {
        Q_ASSERT(hwnd);
        if (!hwnd) {
            return USER_DEFAULT_SCREEN_DPI;
        }
        const DynamicApis &apis = DynamicApis::instance();
        if (apis.pGetDpiForWindow) {         // Win10
            return apis.pGetDpiForWindow(hwnd);
@@ -205,10 +224,6 @@
    }
    static inline quint32 getResizeBorderThickness(HWND hwnd) {
        Q_ASSERT(hwnd);
        if (!hwnd) {
            return 0;
        }
        const DynamicApis &apis = DynamicApis::instance();
        if (apis.pGetSystemMetricsForDpi) {
            const quint32 dpi = getDpiForWindow(hwnd);
@@ -219,11 +234,47 @@
        }
    }
    static inline std::optional<MONITORINFOEXW> getMonitorForWindow(HWND hwnd) {
        Q_ASSERT(hwnd);
        if (!hwnd) {
            return std::nullopt;
    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);
    }
    static inline void updateInternalWindowFrameMargins(HWND hwnd, QWindow *window) {
        const auto margins = [hwnd]() -> QMargins {
            const int titleBarHeight = getTitleBarHeight(hwnd);
            if (isWin10OrGreater()) {
                return {0, -titleBarHeight, 0, 0};
            } else {
                const int frameSize = getResizeBorderThickness(hwnd);
                return {-frameSize, -titleBarHeight, -frameSize, -frameSize};
            }
        }();
        const QVariant marginsVar = QVariant::fromValue(margins);
        window->setProperty("_q_windowsCustomMargins", marginsVar);
#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"),
                                      marginsVar);
            }
        }
#else
        if (const auto platformWindow =
                dynamic_cast<QNativeInterface::Private::QWindowsWindow *>(window->handle())) {
            platformWindow->setCustomMargins(margins);
        }
#endif
    }
    static inline MONITORINFOEXW getMonitorForWindow(HWND hwnd) {
        // Use "MONITOR_DEFAULTTONEAREST" here so that we can still get the correct
        // monitor even if the window is minimized.
        HMONITOR monitor = ::MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
@@ -233,6 +284,16 @@
        return monitorInfo;
    };
    static inline void moveToDesktopCenter(HWND hwnd) {
        const auto monitorInfo = getMonitorForWindow(hwnd);
        RECT windowRect{};
        ::GetWindowRect(hwnd, &windowRect);
        const auto newX = (RECT_WIDTH(monitorInfo.rcMonitor) - RECT_WIDTH(windowRect)) / 2;
        const auto newY = (RECT_HEIGHT(monitorInfo.rcMonitor) - RECT_HEIGHT(windowRect)) / 2;
        ::SetWindowPos(hwnd, nullptr, newX, newY, 0, 0,
                       SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER);
    }
    static inline bool isFullScreen(HWND hwnd) {
        Q_ASSERT(hwnd);
        if (!hwnd) {
@@ -240,9 +301,8 @@
        }
        RECT windowRect{};
        ::GetWindowRect(hwnd, &windowRect);
        const std::optional<MONITORINFOEXW> mi = getMonitorForWindow(hwnd);
        // Compare to the full area of the screen, not the work area.
        return (windowRect == mi.value_or(MONITORINFOEXW{}).rcMonitor);
        return (windowRect == getMonitorForWindow(hwnd).rcMonitor);
    }
    static inline bool isWindowNoState(HWND hwnd) {
@@ -259,6 +319,53 @@
        const auto style = static_cast<DWORD>(::GetWindowLongPtrW(hwnd, GWL_STYLE));
        return (!(style & (WS_MINIMIZE | WS_MAXIMIZE)));
#endif
    }
    static inline void syncPaintEventWithDwm() {
        // No need to sync with DWM if DWM composition is disabled.
        if (!isDwmCompositionEnabled()) {
            return;
        }
        const DynamicApis &apis = DynamicApis::instance();
        // Dirty hack to workaround the resize flicker caused by DWM.
        LARGE_INTEGER freq{};
        ::QueryPerformanceFrequency(&freq);
        TIMECAPS tc{};
        apis.ptimeGetDevCaps(&tc, sizeof(tc));
        const UINT ms_granularity = tc.wPeriodMin;
        apis.ptimeBeginPeriod(ms_granularity);
        LARGE_INTEGER now0{};
        ::QueryPerformanceCounter(&now0);
        // ask DWM where the vertical blank falls
        DWM_TIMING_INFO dti{};
        dti.cbSize = sizeof(dti);
        apis.pDwmGetCompositionTimingInfo(nullptr, &dti);
        LARGE_INTEGER now1{};
        ::QueryPerformanceCounter(&now1);
        // - DWM told us about SOME vertical blank
        //   - past or future, possibly many frames away
        // - convert that into the NEXT vertical blank
        const auto period = qreal(dti.qpcRefreshPeriod);
        const auto dt = qreal(dti.qpcVBlank - now1.QuadPart);
        const qreal ratio = (dt / period);
        auto w = qreal(0);
        auto m = qreal(0);
        if ((dt > qreal(0)) || qFuzzyIsNull(dt)) {
            w = ratio;
        } else {
            // reach back to previous period
            // - so m represents consistent position within phase
            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;
        }
        const qreal m_ms = (qreal(1000) * m / qreal(freq.QuadPart));
        ::Sleep(static_cast<DWORD>(std::round(m_ms)));
        apis.ptimeEndPeriod(ms_granularity);
    }
    static inline QPoint fromNativeLocalPosition(const QWindow *window, const QPoint &point) {
@@ -303,7 +410,7 @@
    }
    static bool isValidWindow(HWND hWnd, bool checkVisible, bool checkTopLevel) {
        if (::IsWindow(hWnd) == FALSE) {
        if (!::IsWindow(hWnd)) {
            return false;
        }
        const LONG_PTR styles = ::GetWindowLongPtrW(hWnd, GWL_STYLE);
@@ -314,15 +421,15 @@
        if (exStyles & WS_EX_TOOLWINDOW) {
            return false;
        }
        RECT rect = {0, 0, 0, 0};
        if (::GetWindowRect(hWnd, &rect) == FALSE) {
        RECT rect{};
        if (!::GetWindowRect(hWnd, &rect)) {
            return false;
        }
        if ((rect.left >= rect.right) || (rect.top >= rect.bottom)) {
            return false;
        }
        if (checkVisible) {
            if (::IsWindowVisible(hWnd) == FALSE) {
            if (!::IsWindowVisible(hWnd)) {
                return false;
            }
        }
@@ -345,21 +452,34 @@
    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.
            if (!result) {
                return false;
            }
            if (lastMessageHandled) {
                *result = static_cast<QT_NATIVE_EVENT_RESULT_TYPE>(lastMessageResult);
                return true;
            // 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.
            auto msg = static_cast<const MSG *>(message);
            if (msg->message == WM_NCCALCSIZE && orgLastMessageContext) {
                LRESULT res;
                if (Win32WindowContext::nonClientCalcSizeHandler(msg->hwnd, msg->message,
                                                                 msg->wParam, msg->lParam, &res)) {
                    *result = decltype(*result)(res);
                    return true;
                }
            }
            return false;
        }
        static bool lastMessageHandled;
        static LRESULT lastMessageResult;
        static WindowsNativeEventFilter *instance;
        static Win32WindowContext *lastMessageContext;
        static inline void install() {
            instance = new WindowsNativeEventFilter();
@@ -373,9 +493,8 @@
        }
    };
    bool WindowsNativeEventFilter::lastMessageHandled = false;
    LRESULT WindowsNativeEventFilter::lastMessageResult = 0;
    WindowsNativeEventFilter *WindowsNativeEventFilter::instance = nullptr;
    Win32WindowContext *WindowsNativeEventFilter::lastMessageContext = nullptr;
    // https://github.com/qt/qtbase/blob/e26a87f1ecc40bc8c6aa5b889fce67410a57a702/src/plugins/platforms/windows/qwindowscontext.cpp#L1025
    // We can see from the source code that Qt will filter out some messages first and then send the
@@ -442,21 +561,30 @@
            return ::DefWindowProcW(hWnd, message, wParam, lParam);
        }
        // Since Qt does the necessary processing of the WM_NCCALCSIZE message, we need to
        // forward it right away and process it in our native event filter.
        if (message == WM_NCCALCSIZE) {
            WindowsNativeEventFilter::lastMessageContext = ctx;
            return ::CallWindowProcW(g_qtWindowProc, hWnd, message, wParam, lParam);
        }
        // Try hooked procedure and save result
        auto &handled = WindowsNativeEventFilter::lastMessageHandled;
        auto &result = WindowsNativeEventFilter::lastMessageResult;
        handled = ctx->windowProc(hWnd, message, wParam, lParam, &result);
        LRESULT result;
        bool handled = ctx->windowProc(hWnd, message, wParam, lParam, &result);
        // TODO: Determine whether to show system menu
        // ...
        // Since Qt does the necessary processing of the message afterward, we still need to
        // continue dispatching it.
        if (handled) {
            return result;
        }
        // Continue dispatching.
        WindowsNativeEventFilter::lastMessageContext = ctx;
        return ::CallWindowProcW(g_qtWindowProc, hWnd, message, wParam, lParam);
    }
    Win32WindowContext::Win32WindowContext(QObject *host, WindowItemDelegate *delegate)
        : AbstractWindowContext(host, delegate) {
    Win32WindowContext::Win32WindowContext() : AbstractWindowContext() {
    }
    Win32WindowContext::~Win32WindowContext() {
@@ -471,14 +599,13 @@
        }
    }
    bool Win32WindowContext::setup() {
        if (!m_windowHandle) {
            return false;
        }
    bool Win32WindowContext::setupHost() {
        // Install window hook
        auto winId = m_windowHandle->winId();
        auto hWnd = reinterpret_cast<HWND>(winId);
        // Inform Qt we want and have set custom margins
        updateInternalWindowFrameMargins(hWnd, m_windowHandle);
        // Store original window proc
        if (!g_qtWindowProc) {
@@ -548,7 +675,7 @@
        const auto &get = [](const int virtualKey) -> bool {
            return (::GetAsyncKeyState(virtualKey) < 0);
        };
        const bool buttonSwapped = (::GetSystemMetrics(SM_SWAPBUTTON) != FALSE);
        const bool buttonSwapped = ::GetSystemMetrics(SM_SWAPBUTTON);
        if (get(VK_LBUTTON)) {
            result |= (buttonSwapped ? MK_RBUTTON : MK_LBUTTON);
        }
@@ -691,9 +818,10 @@
                    // actually lost the hover state. So we filter out these superfluous mouse leave
                    // events here to avoid this issue.
                    DWORD dwScreenPos = ::GetMessagePos();
                    POINT screenPoint{GET_X_LPARAM(dwScreenPos), GET_Y_LPARAM(dwScreenPos)};
                    ::ScreenToClient(hWnd, &screenPoint);
                    QPoint qtScenePos =
                        fromNativeLocalPosition(m_windowHandle, QPoint(GET_X_LPARAM(dwScreenPos),
                                                                       GET_Y_LPARAM(dwScreenPos)));
                        fromNativeLocalPosition(m_windowHandle, {screenPoint.x, screenPoint.y});
                    auto dummy = CoreWindowAgent::Unknown;
                    if (isInSystemButtons(qtScenePos, &dummy)) {
                        // We must record whether the last WM_MOUSELEAVE was filtered, because if
@@ -741,7 +869,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);
@@ -803,7 +931,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;
@@ -818,232 +946,17 @@
    bool Win32WindowContext::customWindowHandler(HWND hWnd, UINT message, WPARAM wParam,
                                                 LPARAM lParam, LRESULT *result) {
        switch (message) {
            case WM_NCCALCSIZE: {
                // Windows是根据这个消息的返回值来设置窗口的客户区(窗口中真正显示的内容)
                // 和非客户区(标题栏、窗口边框、菜单栏和状态栏等Windows系统自行提供的部分
                // ,不过对于Qt来说,除了标题栏和窗口边框,非客户区基本也都是自绘的)的范
                // 围的,lParam里存放的就是新客户区的几何区域,默认是整个窗口的大小,正常
                // 的程序需要修改这个参数,告知系统窗口的客户区和非客户区的范围(一般来说可
                // 以完全交给Windows,让其自行处理,使用默认的客户区和非客户区),因此如果
                // 我们不修改lParam,就可以使客户区充满整个窗口,从而去掉标题栏和窗口边框
                // (因为这些东西都被客户区给盖住了。但边框阴影也会因此而丢失,不过我们会使
                // 用其他方式将其带回,请参考其他消息的处理,此处不过多提及)。但有个情况要
                // 特别注意,那就是窗口最大化后,窗口的实际尺寸会比屏幕的尺寸大一点,从而使
                // 用户看不到窗口的边界,这样用户就不能在窗口最大化后调整窗口的大小了(虽然
                // 这个做法听起来特别奇怪,但Windows确实就是这样做的),因此如果我们要自行
                // 处理窗口的非客户区,就要在窗口最大化后,将窗口边框的宽度和高度(一般是相
                // 等的)从客户区裁剪掉,否则我们窗口所显示的内容就会超出屏幕边界,显示不全。
                // 如果用户开启了任务栏自动隐藏,在窗口最大化后,还要考虑任务栏的位置。因为
                // 如果窗口最大化后,其尺寸和屏幕尺寸相等(因为任务栏隐藏了,所以窗口最大化
                // 后其实是充满了整个屏幕,变相的全屏了),Windows会认为窗口已经进入全屏的
                // 状态,从而导致自动隐藏的任务栏无法弹出。要避免这个状况,就要使窗口的尺寸
                // 小于屏幕尺寸。我下面的做法参考了火狐、Chromium和Windows Terminal
                // 如果没有开启任务栏自动隐藏,是不存在这个问题的,所以要先进行判断。
                // 一般情况下,*result设置为0(相当于DefWindowProc的返回值为0)就可以了,
                // 根据MSDN的说法,返回0意为此消息已经被程序自行处理了,让Windows跳过此消
                // 息,否则Windows会添加对此消息的默认处理,对于当前这个消息而言,就意味着
                // 标题栏和窗口边框又会回来,这当然不是我们想要的结果。根据MSDN,当wParam
                // 为FALSE时,只能返回0,但当其为TRUE时,可以返回0,也可以返回一个WVR_常
                // 量。根据Chromium的注释,当存在非客户区时,如果返回WVR_REDRAW会导致子
                // 窗口/子控件出现奇怪的bug(自绘控件错位),并且Lucas在Windows 10
                // 上成功复现,说明这个bug至今都没有解决。我查阅了大量资料,发现唯一的解决
                // 方案就是返回0。但如果不存在非客户区,且wParam为TRUE,最好返回
                // WVR_REDRAW,否则窗口在调整大小可能会产生严重的闪烁现象。
                // 虽然对大多数消息来说,返回0都代表让Windows忽略此消息,但实际上不同消息
                // 能接受的返回值是不一样的,请注意自行查阅MSDN。
                // Sent when the size and position of a window's client area must be
                // calculated. By processing this message, an application can
                // control the content of the window's client area when the size or
                // position of the window changes. If wParam is TRUE, lParam points
                // to an NCCALCSIZE_PARAMS structure that contains information an
                // application can use to calculate the new size and position of the
                // client rectangle. If wParam is FALSE, lParam points to a RECT
                // structure. On entry, the structure contains the proposed window
                // rectangle for the window. On exit, the structure should contain
                // the screen coordinates of the corresponding window client area.
                // The client area is the window's content area, the non-client area
                // is the area which is provided by the system, such as the title
                // bar, the four window borders, the frame shadow, the menu bar, the
                // status bar, the scroll bar, etc. But for Qt, it draws most of the
                // window area (client + non-client) itself. We now know that the
                // title bar and the window frame is in the non-client area, and we
                // can set the scope of the client area in this message, so we can
                // remove the title bar and the window frame by let the non-client
                // area be covered by the client area (because we can't really get
                // rid of the non-client area, it will always be there, all we can
                // do is to hide it) , which means we should let the client area's
                // size the same with the whole window's size. So there is no room
                // for the non-client area and then the user won't be able to see it
                // again. But how to achieve this? Very easy, just leave lParam (the
                // re-calculated client area) untouched. But of course you can
                // modify lParam, then the non-client area will be seen and the
                // window borders and the window frame will show up. However, things
                // are quite different when you try to modify the top margin of the
                // client area. DWM will always draw the whole title bar no matter
                // what margin value you set for the top, unless you don't modify it
                // and remove the whole top area (the title bar + the one pixel
                // height window border). This can be confirmed in Windows
                // Terminal's source code, you can also try yourself to verify
                // it. So things will become quite complicated if you want to
                // preserve the four window borders.
                // If `wParam` is `FALSE`, `lParam` points to a `RECT` that contains
                // the proposed window rectangle for our window. During our
                // processing of the `WM_NCCALCSIZE` message, we are expected to
                // modify the `RECT` that `lParam` points to, so that its value upon
                // our return is the new client area. We must return 0 if `wParam`
                // is `FALSE`.
                // If `wParam` is `TRUE`, `lParam` points to a `NCCALCSIZE_PARAMS`
                // struct. This struct contains an array of 3 `RECT`s, the first of
                // which has the exact same meaning as the `RECT` that is pointed to
                // by `lParam` when `wParam` is `FALSE`. The remaining `RECT`s, in
                // conjunction with our return value, can
                // be used to specify portions of the source and destination window
                // rectangles that are valid and should be preserved. We opt not to
                // implement an elaborate client-area preservation technique, and
                // simply return 0, which means "preserve the entire old client area
                // and align it with the upper-left corner of our new client area".
                const auto clientRect =
                    ((wParam == FALSE) ? reinterpret_cast<LPRECT>(lParam)
                                       : &(reinterpret_cast<LPNCCALCSIZE_PARAMS>(lParam))->rgrc[0]);
                if (isWin10OrGreater()) {
                    // Store the original top margin before the default window procedure applies the
                    // default frame.
                    const LONG originalTop = clientRect->top;
                    // Apply the default frame because we don't want to remove the whole window
                    // frame, we still need the standard window frame (the resizable frame border
                    // and the frame shadow) for the left, bottom and right edges. If we return 0
                    // here directly, the whole window frame will be removed (which means there will
                    // be no resizable frame border and the frame shadow will also disappear), and
                    // that's also how most applications customize their title bars on Windows. It's
                    // totally OK but since we want to preserve as much original frame as possible,
                    // we can't use that solution.
                    const LRESULT hitTestResult =
                        ::DefWindowProcW(hWnd, WM_NCCALCSIZE, wParam, lParam);
                    if ((hitTestResult != HTERROR) && (hitTestResult != HTNOWHERE)) {
                        *result = hitTestResult;
                        return true;
                    }
                    // Re-apply the original top from before the size of the default frame was
                    // applied, and the whole top frame (the title bar and the top border) is gone
                    // now. For the top frame, we only has 2 choices: (1) remove the top frame
                    // entirely, or (2) don't touch it at all. We can't preserve the top border by
                    // adjusting the top margin here. If we try to modify the top margin, the
                    // original title bar will always be painted by DWM regardless what margin we
                    // set, so here we can only remove the top frame entirely and use some special
                    // technique to bring the top border back.
                    clientRect->top = originalTop;
                }
                const bool max = IsMaximized(hWnd);
                const bool full = isFullScreen(hWnd);
                // We don't need this correction when we're fullscreen. We will
                // have the WS_POPUP size, so we don't have to worry about
                // borders, and the default frame will be fine.
                if (max && !full) {
                    // When a window is maximized, its size is actually a little bit more
                    // than the monitor's work area. The window is positioned and sized in
                    // such a way that the resize handles are outside the monitor and
                    // then the window is clipped to the monitor so that the resize handle
                    // do not appear because you don't need them (because you can't resize
                    // a window when it's maximized unless you restore it).
                    const quint32 frameSize = getResizeBorderThickness(hWnd);
                    clientRect->top += frameSize;
                    if (!isWin10OrGreater()) {
                        clientRect->bottom -= frameSize;
                        clientRect->left += frameSize;
                        clientRect->right -= frameSize;
            case WM_SHOWWINDOW: {
                if (!centered) {
                    // If wParam is TRUE, the window is being shown.
                    // If lParam is zero, the message was sent because of a call to the ShowWindow
                    // function.
                    if (wParam && lParam == 0) {
                        centered = true;
                        moveToDesktopCenter(hWnd);
                    }
                }
                // Attempt to detect if there's an autohide taskbar, and if
                // there is, reduce our size a bit on the side with the taskbar,
                // so the user can still mouse-over the taskbar to reveal it.
                // Make sure to use MONITOR_DEFAULTTONEAREST, so that this will
                // still find the right monitor even when we're restoring from
                // minimized.
                if (max || full) {
                    APPBARDATA abd{};
                    abd.cbSize = sizeof(abd);
                    const UINT taskbarState = ::SHAppBarMessage(ABM_GETSTATE, &abd);
                    // First, check if we have an auto-hide taskbar at all:
                    if (taskbarState & ABS_AUTOHIDE) {
                        bool top = false, bottom = false, left = false, right = false;
                        // Due to ABM_GETAUTOHIDEBAREX was introduced in Windows 8.1,
                        // we have to use another way to judge this if we are running
                        // on Windows 7 or Windows 8.
                        if (isWin8Point1OrGreater()) {
                            const std::optional<MONITORINFOEXW> monitorInfo =
                                getMonitorForWindow(hWnd);
                            const RECT monitorRect = monitorInfo.value().rcMonitor;
                            // This helper can be used to determine if there's an
                            // auto-hide taskbar on the given edge of the monitor
                            // we're currently on.
                            const auto hasAutohideTaskbar = [monitorRect](const UINT edge) -> bool {
                                APPBARDATA abd2{};
                                abd2.cbSize = sizeof(abd2);
                                abd2.uEdge = edge;
                                abd2.rc = monitorRect;
                                const auto hTaskbar = reinterpret_cast<HWND>(
                                    ::SHAppBarMessage(ABM_GETAUTOHIDEBAREX, &abd2));
                                return (hTaskbar != nullptr);
                            };
                            top = hasAutohideTaskbar(ABE_TOP);
                            bottom = hasAutohideTaskbar(ABE_BOTTOM);
                            left = hasAutohideTaskbar(ABE_LEFT);
                            right = hasAutohideTaskbar(ABE_RIGHT);
                        } else {
                            int edge = -1;
                            APPBARDATA abd2{};
                            abd2.cbSize = sizeof(abd2);
                            abd2.hWnd = ::FindWindowW(L"Shell_TrayWnd", nullptr);
                            HMONITOR windowMonitor =
                                ::MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST);
                            HMONITOR taskbarMonitor =
                                ::MonitorFromWindow(abd2.hWnd, MONITOR_DEFAULTTOPRIMARY);
                            if (taskbarMonitor == windowMonitor) {
                                ::SHAppBarMessage(ABM_GETTASKBARPOS, &abd2);
                                edge = int(abd2.uEdge);
                            }
                            top = (edge == ABE_TOP);
                            bottom = (edge == ABE_BOTTOM);
                            left = (edge == ABE_LEFT);
                            right = (edge == ABE_RIGHT);
                        }
                        // If there's a taskbar on any side of the monitor, reduce
                        // our size a little bit on that edge.
                        // Note to future code archeologists:
                        // This doesn't seem to work for fullscreen on the primary
                        // display. However, testing a bunch of other apps with
                        // fullscreen modes and an auto-hiding taskbar has
                        // shown that _none_ of them reveal the taskbar from
                        // fullscreen mode. This includes Edge, Firefox, Chrome,
                        // Sublime Text, PowerPoint - none seemed to support this.
                        // This does however work fine for maximized.
                        if (top) {
                            // Peculiarly, when we're fullscreen,
                            clientRect->top += kAutoHideTaskBarThickness;
                        } else if (bottom) {
                            clientRect->bottom -= kAutoHideTaskBarThickness;
                        } else if (left) {
                            clientRect->left += kAutoHideTaskBarThickness;
                        } else if (right) {
                            clientRect->right -= kAutoHideTaskBarThickness;
                        }
                    }
                }
                // ### TODO: std::ignore = Utils::syncWmPaintWithDwm(); // This should be executed
                // at the very last. By returning WVR_REDRAW we can make the window resizing look
                // less broken. But we must return 0 if wParam is FALSE, according to Microsoft
                // Docs.
                // **IMPORTANT NOTE**:
                // If you are drawing something manually through D3D in your window, don't
                // try to return WVR_REDRAW here, otherwise Windows exhibits bugs where
                // client pixels and child windows are mispositioned by the width/height
                // of the upper-left non-client area. It's confirmed that this issue exists
                // from Windows 7 to Windows 10. Not tested on Windows 11 yet. Don't know
                // whether it exists on Windows XP to Windows Vista or not.
                *result = wParam == FALSE ? FALSE : WVR_REDRAW;
                return true;
                break;
            }
            case WM_NCHITTEST: {
                // 原生Win32窗口只有顶边是在窗口内部resize的,其余三边都是在窗口
@@ -1145,7 +1058,7 @@
                bool isFixedSize = m_delegate->isHostSizeFixed(m_host);
                bool isTitleBar = isInTitleBarDraggableArea(qtScenePos);
                bool dontOverrideCursor = false;                  // ### TODO
                bool dontOverrideCursor = false; // ### TODO
                CoreWindowAgent::SystemButton sysButtonType = CoreWindowAgent::Unknown;
                if (!isFixedSize && isInSystemButtons(qtScenePos, &sysButtonType)) {
@@ -1196,8 +1109,6 @@
                                break;
                            case CoreWindowAgent::Close:
                                *result = HTCLOSE;
                                break;
                            case CoreWindowAgent::Unknown:
                                break;
                            default:
                                break; // unreachable
@@ -1260,6 +1171,7 @@
                        return true;
                    }
                    *result = HTCLIENT;
                    return true;
                } else {
                    if (full) {
                        *result = HTCLIENT;
@@ -1321,8 +1233,8 @@
                        return true;
                    }
                    *result = HTCLIENT;
                    return true;
                }
                return true;
            }
            default:
                break;
@@ -1359,10 +1271,10 @@
                        // window activation state change.
                        *result = ::DefWindowProcW(hWnd, WM_NCACTIVATE, wParam, -1);
                    } else {
                        if (wParam == FALSE) {
                            *result = TRUE;
                        } else {
                        if (wParam) {
                            *result = FALSE;
                        } else {
                            *result = TRUE;
                        }
                    }
                    return true;
@@ -1390,4 +1302,231 @@
        return false;
    }
}
    bool Win32WindowContext::nonClientCalcSizeHandler(HWND hWnd, UINT message, WPARAM wParam,
                                                      LPARAM lParam, LRESULT *result) {
        Q_UNUSED(message)
        // Windows是根据这个消息的返回值来设置窗口的客户区(窗口中真正显示的内容)
        // 和非客户区(标题栏、窗口边框、菜单栏和状态栏等Windows系统自行提供的部分
        // ,不过对于Qt来说,除了标题栏和窗口边框,非客户区基本也都是自绘的)的范
        // 围的,lParam里存放的就是新客户区的几何区域,默认是整个窗口的大小,正常
        // 的程序需要修改这个参数,告知系统窗口的客户区和非客户区的范围(一般来说可
        // 以完全交给Windows,让其自行处理,使用默认的客户区和非客户区),因此如果
        // 我们不修改lParam,就可以使客户区充满整个窗口,从而去掉标题栏和窗口边框
        // (因为这些东西都被客户区给盖住了。但边框阴影也会因此而丢失,不过我们会使
        // 用其他方式将其带回,请参考其他消息的处理,此处不过多提及)。但有个情况要
        // 特别注意,那就是窗口最大化后,窗口的实际尺寸会比屏幕的尺寸大一点,从而使
        // 用户看不到窗口的边界,这样用户就不能在窗口最大化后调整窗口的大小了(虽然
        // 这个做法听起来特别奇怪,但Windows确实就是这样做的),因此如果我们要自行
        // 处理窗口的非客户区,就要在窗口最大化后,将窗口边框的宽度和高度(一般是相
        // 等的)从客户区裁剪掉,否则我们窗口所显示的内容就会超出屏幕边界,显示不全。
        // 如果用户开启了任务栏自动隐藏,在窗口最大化后,还要考虑任务栏的位置。因为
        // 如果窗口最大化后,其尺寸和屏幕尺寸相等(因为任务栏隐藏了,所以窗口最大化
        // 后其实是充满了整个屏幕,变相的全屏了),Windows会认为窗口已经进入全屏的
        // 状态,从而导致自动隐藏的任务栏无法弹出。要避免这个状况,就要使窗口的尺寸
        // 小于屏幕尺寸。我下面的做法参考了火狐、Chromium和Windows Terminal
        // 如果没有开启任务栏自动隐藏,是不存在这个问题的,所以要先进行判断。
        // 一般情况下,*result设置为0(相当于DefWindowProc的返回值为0)就可以了,
        // 根据MSDN的说法,返回0意为此消息已经被程序自行处理了,让Windows跳过此消
        // 息,否则Windows会添加对此消息的默认处理,对于当前这个消息而言,就意味着
        // 标题栏和窗口边框又会回来,这当然不是我们想要的结果。根据MSDN,当wParam
        // 为FALSE时,只能返回0,但当其为TRUE时,可以返回0,也可以返回一个WVR_常
        // 量。根据Chromium的注释,当存在非客户区时,如果返回WVR_REDRAW会导致子
        // 窗口/子控件出现奇怪的bug(自绘控件错位),并且Lucas在Windows 10
        // 上成功复现,说明这个bug至今都没有解决。我查阅了大量资料,发现唯一的解决
        // 方案就是返回0。但如果不存在非客户区,且wParam为TRUE,最好返回
        // WVR_REDRAW,否则窗口在调整大小可能会产生严重的闪烁现象。
        // 虽然对大多数消息来说,返回0都代表让Windows忽略此消息,但实际上不同消息
        // 能接受的返回值是不一样的,请注意自行查阅MSDN。
        // Sent when the size and position of a window's client area must be
        // calculated. By processing this message, an application can
        // control the content of the window's client area when the size or
        // position of the window changes. If wParam is TRUE, lParam points
        // to an NCCALCSIZE_PARAMS structure that contains information an
        // application can use to calculate the new size and position of the
        // client rectangle. If wParam is FALSE, lParam points to a RECT
        // structure. On entry, the structure contains the proposed window
        // rectangle for the window. On exit, the structure should contain
        // the screen coordinates of the corresponding window client area.
        // The client area is the window's content area, the non-client area
        // is the area which is provided by the system, such as the title
        // bar, the four window borders, the frame shadow, the menu bar, the
        // status bar, the scroll bar, etc. But for Qt, it draws most of the
        // window area (client + non-client) itself. We now know that the
        // title bar and the window frame is in the non-client area, and we
        // can set the scope of the client area in this message, so we can
        // remove the title bar and the window frame by let the non-client
        // area be covered by the client area (because we can't really get
        // rid of the non-client area, it will always be there, all we can
        // do is to hide it) , which means we should let the client area's
        // size the same with the whole window's size. So there is no room
        // for the non-client area and then the user won't be able to see it
        // again. But how to achieve this? Very easy, just leave lParam (the
        // re-calculated client area) untouched. But of course you can
        // modify lParam, then the non-client area will be seen and the
        // window borders and the window frame will show up. However, things
        // are quite different when you try to modify the top margin of the
        // client area. DWM will always draw the whole title bar no matter
        // what margin value you set for the top, unless you don't modify it
        // and remove the whole top area (the title bar + the one pixel
        // height window border). This can be confirmed in Windows
        // Terminal's source code, you can also try yourself to verify
        // it. So things will become quite complicated if you want to
        // preserve the four window borders.
        // If `wParam` is `FALSE`, `lParam` points to a `RECT` that contains
        // the proposed window rectangle for our window. During our
        // processing of the `WM_NCCALCSIZE` message, we are expected to
        // modify the `RECT` that `lParam` points to, so that its value upon
        // our return is the new client area. We must return 0 if `wParam`
        // is `FALSE`.
        // If `wParam` is `TRUE`, `lParam` points to a `NCCALCSIZE_PARAMS`
        // struct. This struct contains an array of 3 `RECT`s, the first of
        // which has the exact same meaning as the `RECT` that is pointed to
        // by `lParam` when `wParam` is `FALSE`. The remaining `RECT`s, in
        // conjunction with our return value, can
        // be used to specify portions of the source and destination window
        // rectangles that are valid and should be preserved. We opt not to
        // implement an elaborate client-area preservation technique, and
        // simply return 0, which means "preserve the entire old client area
        // and align it with the upper-left corner of our new client area".
        const auto clientRect = wParam ? &(reinterpret_cast<LPNCCALCSIZE_PARAMS>(lParam))->rgrc[0]
                                       : reinterpret_cast<LPRECT>(lParam);
        if (isWin10OrGreater()) {
            // Store the original top margin before the default window procedure applies the
            // default frame.
            const LONG originalTop = clientRect->top;
            // Apply the default frame because we don't want to remove the whole window
            // frame, we still need the standard window frame (the resizable frame border
            // and the frame shadow) for the left, bottom and right edges. If we return 0
            // here directly, the whole window frame will be removed (which means there will
            // be no resizable frame border and the frame shadow will also disappear), and
            // that's also how most applications customize their title bars on Windows. It's
            // totally OK but since we want to preserve as much original frame as possible,
            // we can't use that solution.
            const LRESULT hitTestResult = ::DefWindowProcW(hWnd, WM_NCCALCSIZE, wParam, lParam);
            if ((hitTestResult != HTERROR) && (hitTestResult != HTNOWHERE)) {
                *result = hitTestResult;
                return true;
            }
            // Re-apply the original top from before the size of the default frame was
            // applied, and the whole top frame (the title bar and the top border) is gone
            // now. For the top frame, we only has 2 choices: (1) remove the top frame
            // entirely, or (2) don't touch it at all. We can't preserve the top border by
            // adjusting the top margin here. If we try to modify the top margin, the
            // original title bar will always be painted by DWM regardless what margin we
            // set, so here we can only remove the top frame entirely and use some special
            // technique to bring the top border back.
            clientRect->top = originalTop;
        }
        const bool max = IsMaximized(hWnd);
        const bool full = isFullScreen(hWnd);
        // We don't need this correction when we're fullscreen. We will
        // have the WS_POPUP size, so we don't have to worry about
        // borders, and the default frame will be fine.
        if (max && !full) {
            // When a window is maximized, its size is actually a little bit more
            // than the monitor's work area. The window is positioned and sized in
            // such a way that the resize handles are outside the monitor and
            // then the window is clipped to the monitor so that the resize handle
            // do not appear because you don't need them (because you can't resize
            // a window when it's maximized unless you restore it).
            const quint32 frameSize = getResizeBorderThickness(hWnd);
            clientRect->top += frameSize;
            if (!isWin10OrGreater()) {
                clientRect->bottom -= frameSize;
                clientRect->left += frameSize;
                clientRect->right -= frameSize;
            }
        }
        // Attempt to detect if there's an autohide taskbar, and if
        // there is, reduce our size a bit on the side with the taskbar,
        // so the user can still mouse-over the taskbar to reveal it.
        // Make sure to use MONITOR_DEFAULTTONEAREST, so that this will
        // still find the right monitor even when we're restoring from
        // minimized.
        if (max || full) {
            APPBARDATA abd{};
            abd.cbSize = sizeof(abd);
            const UINT taskbarState = ::SHAppBarMessage(ABM_GETSTATE, &abd);
            // First, check if we have an auto-hide taskbar at all:
            if (taskbarState & ABS_AUTOHIDE) {
                bool top = false, bottom = false, left = false, right = false;
                // Due to ABM_GETAUTOHIDEBAREX was introduced in Windows 8.1,
                // we have to use another way to judge this if we are running
                // on Windows 7 or Windows 8.
                if (isWin8Point1OrGreater()) {
                    const RECT monitorRect = getMonitorForWindow(hWnd).rcMonitor;
                    // This helper can be used to determine if there's an
                    // auto-hide taskbar on the given edge of the monitor
                    // we're currently on.
                    const auto hasAutohideTaskbar = [monitorRect](const UINT edge) -> bool {
                        APPBARDATA abd2{};
                        abd2.cbSize = sizeof(abd2);
                        abd2.uEdge = edge;
                        abd2.rc = monitorRect;
                        const auto hTaskbar =
                            reinterpret_cast<HWND>(::SHAppBarMessage(ABM_GETAUTOHIDEBAREX, &abd2));
                        return (hTaskbar != nullptr);
                    };
                    top = hasAutohideTaskbar(ABE_TOP);
                    bottom = hasAutohideTaskbar(ABE_BOTTOM);
                    left = hasAutohideTaskbar(ABE_LEFT);
                    right = hasAutohideTaskbar(ABE_RIGHT);
                } else {
                    int edge = -1;
                    APPBARDATA abd2{};
                    abd2.cbSize = sizeof(abd2);
                    abd2.hWnd = ::FindWindowW(L"Shell_TrayWnd", nullptr);
                    HMONITOR windowMonitor = ::MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST);
                    HMONITOR taskbarMonitor =
                        ::MonitorFromWindow(abd2.hWnd, MONITOR_DEFAULTTOPRIMARY);
                    if (taskbarMonitor == windowMonitor) {
                        ::SHAppBarMessage(ABM_GETTASKBARPOS, &abd2);
                        edge = int(abd2.uEdge);
                    }
                    top = (edge == ABE_TOP);
                    bottom = (edge == ABE_BOTTOM);
                    left = (edge == ABE_LEFT);
                    right = (edge == ABE_RIGHT);
                }
                // If there's a taskbar on any side of the monitor, reduce
                // our size a little bit on that edge.
                // Note to future code archeologists:
                // This doesn't seem to work for fullscreen on the primary
                // display. However, testing a bunch of other apps with
                // fullscreen modes and an auto-hiding taskbar has
                // shown that _none_ of them reveal the taskbar from
                // fullscreen mode. This includes Edge, Firefox, Chrome,
                // Sublime Text, PowerPoint - none seemed to support this.
                // This does however work fine for maximized.
                if (top) {
                    // Peculiarly, when we're fullscreen,
                    clientRect->top += kAutoHideTaskBarThickness;
                } else if (bottom) {
                    clientRect->bottom -= kAutoHideTaskBarThickness;
                } else if (left) {
                    clientRect->left += kAutoHideTaskBarThickness;
                } else if (right) {
                    clientRect->right -= kAutoHideTaskBarThickness;
                }
            }
        }
        // We should call this function only before the function returns.
        syncPaintEventWithDwm();
        // By returning WVR_REDRAW we can make the window resizing look
        // less broken. But we must return 0 if wParam is FALSE, according to Microsoft
        // Docs.
        // **IMPORTANT NOTE**:
        // If you are drawing something manually through D3D in your window, don't
        // try to return WVR_REDRAW here, otherwise Windows exhibits bugs where
        // client pixels and child windows are mispositioned by the width/height
        // of the upper-left non-client area. It's confirmed that this issue exists
        // from Windows 7 to Windows 10. Not tested on Windows 11 yet. Don't know
        // whether it exists on Windows XP to Windows Vista or not.
        *result = wParam ? WVR_REDRAW : FALSE;
        return true;
    }
}