Yuhang Zhao
2023-12-07 3d50f3133ddbbd7136dca8173cf9164b78121823
src/core/contexts/win32windowcontext.cpp
@@ -23,6 +23,7 @@
#include <shellscalingapi.h>
#include <dwmapi.h>
#include <timeapi.h>
Q_DECLARE_METATYPE(QMargins)
@@ -45,9 +46,13 @@
    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"));
@@ -64,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;
@@ -284,10 +295,6 @@
    }
    static inline bool isFullScreen(HWND hwnd) {
        Q_ASSERT(hwnd);
        if (!hwnd) {
            return false;
        }
        RECT windowRect{};
        ::GetWindowRect(hwnd, &windowRect);
        // Compare to the full area of the screen, not the work area.
@@ -295,10 +302,6 @@
    }
    static inline bool isWindowNoState(HWND hwnd) {
        Q_ASSERT(hwnd);
        if (!hwnd) {
            return false;
        }
#if 0
        WINDOWPLACEMENT wp{};
        wp.length = sizeof(wp);
@@ -310,16 +313,49 @@
#endif
    }
    static inline QPoint fromNativeLocalPosition(const QWindow *window, const QPoint &point) {
        Q_ASSERT(window);
        if (!window) {
            return point;
    static inline void syncPaintEventWithDwm() {
        // No need to sync with DWM if DWM composition is disabled.
        if (!isDwmCompositionEnabled()) {
            return;
        }
#if 1
        return QHighDpi::fromNativeLocalPosition(point, window);
#else
        return QPointF(QPointF(point) / window->devicePixelRatio()).toPoint();
#endif
        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));
        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 Win32WindowContext::WindowPart getHitWindowPart(int hitTestResult) {
@@ -408,7 +444,7 @@
            // 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 = reinterpret_cast<const MSG *>(message);
            auto msg = static_cast<const MSG *>(message);
            if (msg->message == WM_NCCALCSIZE && orgLastMessageContext) {
                LRESULT res;
                if (Win32WindowContext::nonClientCalcSizeHandler(msg->hwnd, msg->message,
@@ -424,11 +460,17 @@
        static Win32WindowContext *lastMessageContext;
        static inline void install() {
            if (instance) {
                return;
            }
            instance = new WindowsNativeEventFilter();
            installNativeEventFilter(instance);
        }
        static inline void uninstall() {
            if (!instance) {
                return;
            }
            removeNativeEventFilter(instance);
            delete instance;
            instance = nullptr;
@@ -558,9 +600,7 @@
        ::SetWindowLongPtrW(hWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(QWKHookedWndProc));
        // Install global native event filter
        if (!WindowsNativeEventFilter::instance) {
            WindowsNativeEventFilter::install();
        }
        WindowsNativeEventFilter::install();
        // Cache window ID
        windowId = winId;
@@ -763,7 +803,7 @@
                    POINT screenPoint{GET_X_LPARAM(dwScreenPos), GET_Y_LPARAM(dwScreenPos)};
                    ::ScreenToClient(hWnd, &screenPoint);
                    QPoint qtScenePos =
                        fromNativeLocalPosition(m_windowHandle, {screenPoint.x, screenPoint.y});
                        QHighDpi::fromNativeLocalPosition(QPoint{screenPoint.x, screenPoint.y}, m_windowHandle);
                    auto dummy = CoreWindowAgent::Unknown;
                    if (isInSystemButtons(qtScenePos, &dummy)) {
                        // We must record whether the last WM_MOUSELEAVE was filtered, because if
@@ -811,7 +851,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);
@@ -873,7 +913,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;
@@ -995,8 +1035,8 @@
                auto clientWidth = RECT_WIDTH(clientRect);
                auto clientHeight = RECT_HEIGHT(clientRect);
                QPoint qtScenePos = fromNativeLocalPosition(
                    m_windowHandle, QPoint(nativeLocalPos.x, nativeLocalPos.y));
                QPoint qtScenePos = QHighDpi::fromNativeLocalPosition(
                    QPoint(nativeLocalPos.x, nativeLocalPos.y), m_windowHandle);
                bool isFixedSize = m_delegate->isHostSizeFixed(m_host);
                bool isTitleBar = isInTitleBarDraggableArea(qtScenePos);
@@ -1455,8 +1495,9 @@
                }
            }
        }
        // ### TODO: std::ignore = Utils::syncWmPaintWithDwm(); // This should be executed
        // at the very last. By returning WVR_REDRAW we can make the window resizing look
        // 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**: