Yuhang Zhao
2023-12-06 8521bbf2f335984542f2071825d7383548862b7a
src/core/contexts/win32windowcontext.cpp
@@ -1,4 +1,5 @@
#include "win32windowcontext_p.h"
#include "qwkcoreglobal_p.h"
#include <optional>
@@ -7,14 +8,24 @@
#include <QtCore/QCoreApplication>
#include <QtCore/QOperatingSystemVersion>
#include <QtCore/QScopeGuard>
#include <QtCore/QTimer>
#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>
Q_DECLARE_METATYPE(QMargins)
namespace QWK {
@@ -26,8 +37,15 @@
    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(&::GetDpiForWindow) pGetDpiForWindow = nullptr;
        decltype(&::GetSystemMetricsForDpi) pGetSystemMetricsForDpi = nullptr;
        decltype(&::GetDpiForMonitor) pGetDpiForMonitor = nullptr;
@@ -45,6 +63,8 @@
            QSystemLibrary dwmapi(QStringLiteral("dwmapi.dll"));
            pDwmFlush = reinterpret_cast<decltype(pDwmFlush)>(dwmapi.resolve("DwmFlush"));
            pDwmIsCompositionEnabled = reinterpret_cast<decltype(pDwmIsCompositionEnabled)>(
                dwmapi.resolve("DwmIsCompositionEnabled"));
        }
        ~DynamicApis() = default;
@@ -138,6 +158,12 @@
        return hwnd2str(reinterpret_cast<WId>(hwnd));
    }
    static inline bool isWin8OrGreater() {
        static const bool result =
            QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows8;
        return result;
    }
    static inline bool isWin8Point1OrGreater() {
        static const bool result =
            QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows8_1;
@@ -150,10 +176,32 @@
        return result;
    }
    static inline bool isDwmCompositionEnabled() {
        if (isWin8OrGreater()) {
            return true;
        }
        const DynamicApis &apis = DynamicApis::instance();
        if (!apis.pDwmIsCompositionEnabled) {
            return false;
        }
        BOOL enabled = FALSE;
        return SUCCEEDED(apis.pDwmIsCompositionEnabled(&enabled)) && enabled;
    }
    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;
            return 0;
        }
        const DynamicApis &apis = DynamicApis::instance();
        if (apis.pGetDpiForWindow) {         // Win10
@@ -186,6 +234,53 @@
        } else {
            return ::GetSystemMetrics(SM_CXSIZEFRAME) + ::GetSystemMetrics(SM_CXPADDEDBORDER);
        }
    }
    static inline quint32 getTitleBarHeight(HWND hwnd) {
        Q_ASSERT(hwnd);
        if (!hwnd) {
            return 0;
        }
        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) {
        Q_ASSERT(hwnd);
        Q_ASSERT(window);
        if (!hwnd || !window) {
            return;
        }
        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 std::optional<MONITORINFOEXW> getMonitorForWindow(HWND hwnd) {
@@ -266,7 +361,7 @@
            case HTBORDER:
                return Win32WindowContext::FixedBorder;
            default:
                break;
                break; // unreachable
        }
        return Win32WindowContext::Outside;
    }
@@ -424,8 +519,8 @@
        return ::CallWindowProcW(g_qtWindowProc, hWnd, message, wParam, lParam);
    }
    Win32WindowContext::Win32WindowContext(QWindow *window, WindowItemDelegate *delegate)
        : AbstractWindowContext(window, delegate) {
    Win32WindowContext::Win32WindowContext(QObject *host, WindowItemDelegate *delegate)
        : AbstractWindowContext(host, delegate) {
    }
    Win32WindowContext::~Win32WindowContext() {
@@ -441,10 +536,16 @@
    }
    bool Win32WindowContext::setup() {
        auto winId = m_windowHandle->winId();
        if (!m_windowHandle) {
            return false;
        }
        // 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) {
@@ -464,6 +565,8 @@
        // Save window handle mapping
        g_wndProcHash->insert(hWnd, this);
        QTimer::singleShot(0, m_windowHandle, [hWnd](){ triggerFrameChange(hWnd); });
        return true;
    }
@@ -1087,7 +1190,7 @@
                // window can behave just like a normal Win32 window even if we now
                // doesn't have a title bar at all. Most importantly, the flicker and
                // jitter during window resizing is totally gone now. The disadvantage
                // is we have to draw a top frame border ourself. Previously I thought
                // is we have to draw a top frame border ourselves. Previously I thought
                // we have to do the black magic in WM_PAINT just like what Windows
                // Terminal does, however, later I found that if we choose a proper
                // color, our homemade top border can almost have exactly the same
@@ -1109,7 +1212,7 @@
                QPoint qtScenePos = fromNativeLocalPosition(
                    m_windowHandle, QPoint(nativeLocalPos.x, nativeLocalPos.y));
                bool isFixedSize = /*isWindowFixedSize()*/ false; // ### FIXME
                bool isFixedSize = m_delegate->isHostSizeFixed(m_host);
                bool isTitleBar = isInTitleBarDraggableArea(qtScenePos);
                bool dontOverrideCursor = false;                  // ### TODO
@@ -1165,6 +1268,8 @@
                                break;
                            case CoreWindowAgent::Unknown:
                                break;
                            default:
                                break; // unreachable
                        }
                    }
                    if (*result == HTNOWHERE) {
@@ -1224,6 +1329,7 @@
                        return true;
                    }
                    *result = HTCLIENT;
                    return true;
                } else {
                    if (full) {
                        *result = HTCLIENT;
@@ -1285,12 +1391,72 @@
                        return true;
                    }
                    *result = HTCLIENT;
                    return true;
                }
                return true;
            }
            default:
                break;
        }
        if (!isWin10OrGreater()) {
            switch (message) {
                case WM_NCUAHDRAWCAPTION:
                case WM_NCUAHDRAWFRAME: {
                    // These undocumented messages are sent to draw themed window
                    // borders. Block them to prevent drawing borders over the client
                    // area.
                    *result = FALSE;
                    return true;
                }
                case WM_NCPAINT: {
                    // 边框阴影处于非客户区的范围,因此如果直接阻止非客户区的绘制,会导致边框阴影丢失
                    if (!isDwmCompositionEnabled()) {
                        // Only block WM_NCPAINT when DWM composition is disabled. If
                        // it's blocked when DWM composition is enabled, the frame
                        // shadow won't be drawn.
                        *result = FALSE;
                        return true;
                    } else {
                        break;
                    }
                }
                case WM_NCACTIVATE: {
                    if (isDwmCompositionEnabled()) {
                        // DefWindowProc won't repaint the window border if lParam (normally a HRGN)
                        // is -1. See the following link's "lParam" section:
                        // https://docs.microsoft.com/en-us/windows/win32/winmsg/wm-ncactivate
                        // Don't use "*result = 0" here, otherwise the window won't respond to the
                        // window activation state change.
                        *result = ::DefWindowProcW(hWnd, WM_NCACTIVATE, wParam, -1);
                    } else {
                        if (wParam == FALSE) {
                            *result = TRUE;
                        } else {
                            *result = FALSE;
                        }
                    }
                    return true;
                }
                case WM_SETICON:
                case WM_SETTEXT: {
                    // Disable painting while these messages are handled to prevent them
                    // from drawing a window caption over the client area.
                    const auto oldStyle = static_cast<DWORD>(::GetWindowLongPtrW(hWnd, GWL_STYLE));
                    // Prevent Windows from drawing the default title bar by temporarily
                    // toggling the WS_VISIBLE style.
                    const DWORD newStyle = (oldStyle & ~WS_VISIBLE);
                    ::SetWindowLongPtrW(hWnd, GWL_STYLE, static_cast<LONG_PTR>(newStyle));
                    triggerFrameChange(hWnd);
                    const LRESULT originalResult = ::DefWindowProcW(hWnd, message, wParam, lParam);
                    ::SetWindowLongPtrW(hWnd, GWL_STYLE, static_cast<LONG_PTR>(oldStyle));
                    triggerFrameChange(hWnd);
                    *result = originalResult;
                    return true;
                }
                default:
                    break;
            }
        }
        return false;
    }