From 46285db399f75154ad6c451e12f2cae2e59ace25 Mon Sep 17 00:00:00 2001
From: Sine Striker <trueful@163.com>
Date: 周日, 24 12月 2023 23:52:03 +0800
Subject: [PATCH] Use brilliant workaround to show Windows 10 top border

---
 src/core/contexts/win32windowcontext_p.h    |    3 +
 src/widgets/widgetwindowagent_win.cpp       |   43 +++++++++++----------
 src/core/kernel/nativeeventfilter.cpp       |    4 ++
 src/core/kernel/nativeeventfilter_p.h       |    3 +
 src/core/contexts/abstractwindowcontext_p.h |    3 +
 src/core/shared/qwkwindowsextra_p.h         |    3 -
 examples/mainwindow/mainwindow.cpp          |    8 ++--
 src/core/contexts/win32windowcontext.cpp    |   51 ++++++++++++++++++++++---
 8 files changed, 85 insertions(+), 33 deletions(-)

diff --git a/examples/mainwindow/mainwindow.cpp b/examples/mainwindow/mainwindow.cpp
index 8c224f3..a1a39dd 100644
--- a/examples/mainwindow/mainwindow.cpp
+++ b/examples/mainwindow/mainwindow.cpp
@@ -115,6 +115,10 @@
     windowAgent = new QWK::WidgetWindowAgent(this);
     windowAgent->setup(this);
 
+#ifdef Q_OS_WIN
+    windowAgent->setWindowAttribute(QStringLiteral("dark-mode"), true);
+#endif
+
     // 2. Construct your title bar
     auto menuBar = [this]() {
         auto menuBar = new QMenuBar();
@@ -298,10 +302,6 @@
     if (!styleSheet().isEmpty() && theme == currentTheme)
         return;
     currentTheme = theme;
-
-#ifdef Q_OS_WIN
-    windowAgent->setWindowAttribute(QStringLiteral("dark-mode"), currentTheme == Dark);
-#endif
 
     if (QFile qss(theme == Dark ? QStringLiteral(":/dark-style.qss")
                                 : QStringLiteral(":/light-style.qss"));
diff --git a/src/core/contexts/abstractwindowcontext_p.h b/src/core/contexts/abstractwindowcontext_p.h
index db4b5cc..f8ea17e 100644
--- a/src/core/contexts/abstractwindowcontext_p.h
+++ b/src/core/contexts/abstractwindowcontext_p.h
@@ -64,7 +64,8 @@
             RaiseWindowHook,
             ShowSystemMenuHook,
             DefaultColorsHook,
-            DrawWindows10BorderHook,     // Only works on Windows 10
+            DrawWindows10BorderHook,     // Only works on Windows 10, emulated workaround
+            DrawWindows10BorderHook2,    // Only works on Windows 10, native workaround
             SystemButtonAreaChangedHook, // Only works on Mac
         };
         virtual void virtual_hook(int id, void *data);
diff --git a/src/core/contexts/win32windowcontext.cpp b/src/core/contexts/win32windowcontext.cpp
index d78ce81..9b92b0d 100644
--- a/src/core/contexts/win32windowcontext.cpp
+++ b/src/core/contexts/win32windowcontext.cpp
@@ -5,6 +5,7 @@
 #include <QtCore/QHash>
 #include <QtCore/QScopeGuard>
 #include <QtCore/QTimer>
+#include <QtCore/QDateTime>
 #include <QtGui/QGuiApplication>
 #include <QtGui/QPainter>
 #include <QtGui/QPalette>
@@ -658,6 +659,7 @@
             }
 
             case DrawWindows10BorderHook: {
+#if QWINDOWKIT_CONFIG(ENABLE_WINDOWS_SYSTEM_BORDER)
                 if (!windowId)
                     return;
 
@@ -668,7 +670,7 @@
                 const auto hwnd = reinterpret_cast<HWND>(windowId);
 
                 QPen pen;
-                pen.setWidth(getWindowFrameBorderThickness(hwnd) * 2);
+                pen.setWidth(int(getWindowFrameBorderThickness(hwnd)) * 2);
 
                 const bool dark = isDarkThemeActive() && isDarkWindowFrameEnabled(hwnd);
                 if (m_delegate->isWindowActive(m_host)) {
@@ -688,7 +690,7 @@
                 }
                 painter.save();
 
-                // We needs anti-aliasing to give us better result.
+                // We need antialiasing to give us better result.
                 painter.setRenderHint(QPainter::Antialiasing);
 
                 painter.setPen(pen);
@@ -697,6 +699,33 @@
                     QPoint{m_windowHandle->width(), 0}
                 });
                 painter.restore();
+                return;
+#endif
+            }
+
+            case DrawWindows10BorderHook2: {
+#if QWINDOWKIT_CONFIG(ENABLE_WINDOWS_SYSTEM_BORDER)
+                if (!m_windowHandle)
+                    return;
+
+                // https://github.com/microsoft/terminal/blob/71a6f26e6ece656084e87de1a528c4a8072eeabd/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp#L1025
+                // https://docs.microsoft.com/en-us/windows/win32/dwm/customframe#extending-the-client-frame
+                // Draw a black rectangle to make Windows native top border show
+
+                auto hWnd = reinterpret_cast<HWND>(windowId);
+                HDC hdc = ::GetDC(hWnd);
+                RECT windowRect{};
+                ::GetClientRect(hWnd, &windowRect);
+                RECT rcTopBorder = {
+                    0,
+                    0,
+                    RECT_WIDTH(windowRect),
+                    int(getWindowFrameBorderThickness(hWnd)),
+                };
+                ::FillRect(hdc, &rcTopBorder,
+                           reinterpret_cast<HBRUSH>(::GetStockObject(BLACK_BRUSH)));
+                ::ReleaseDC(hWnd, hdc);
+#endif
                 return;
             }
 
@@ -712,7 +741,15 @@
     }
 
     int Win32WindowContext::borderThickness() const {
-        return getWindowFrameBorderThickness(reinterpret_cast<HWND>(windowId));
+        return int(getWindowFrameBorderThickness(reinterpret_cast<HWND>(windowId)));
+    }
+
+    void Win32WindowContext::resume(const QByteArray &eventType, void *message,
+                                    QT_NATIVE_EVENT_RESULT_TYPE *result) {
+        const auto msg = static_cast<const MSG *>(message);
+        LRESULT res =
+            ::CallWindowProcW(g_qtWindowProc, msg->hwnd, msg->message, msg->wParam, msg->lParam);
+        *result = decltype(*result)(res);
     }
 
     void Win32WindowContext::winIdChanged() {
@@ -732,6 +769,11 @@
 
         if (!isSystemBorderEnabled()) {
             static constexpr const MARGINS margins = {1, 1, 1, 1};
+            DynamicApis::instance().pDwmExtendFrameIntoClientArea(hWnd, &margins);
+        } else if (isWin10OrGreater() && !isWin11OrGreater()) {
+            // https://github.com/microsoft/terminal/blob/71a6f26e6ece656084e87de1a528c4a8072eeabd/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp#L940
+            // Must call DWM API to extend top frame to client area
+            static constexpr const MARGINS margins = {0, 0, 1, 0};
             DynamicApis::instance().pDwmExtendFrameIntoClientArea(hWnd, &margins);
         }
 
@@ -956,13 +998,11 @@
             }
 
             BOOL enable = attribute.toBool();
-
             if (isWin101903OrGreater()) {
                 apis.pSetPreferredAppMode(enable ? PAM_AUTO : PAM_DEFAULT);
             } else {
                 apis.pAllowDarkModeForApp(enable);
             }
-
             for (const auto attr : {
                      _DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1,
                      _DWMWA_USE_IMMERSIVE_DARK_MODE,
@@ -971,7 +1011,6 @@
             }
 
             apis.pFlushMenuThemes();
-
             return true;
         }
         return false;
diff --git a/src/core/contexts/win32windowcontext_p.h b/src/core/contexts/win32windowcontext_p.h
index d5c6d13..3af0e13 100644
--- a/src/core/contexts/win32windowcontext_p.h
+++ b/src/core/contexts/win32windowcontext_p.h
@@ -38,6 +38,9 @@
         bool needBorderPainter() const;
         int borderThickness() const;
 
+        void resume(const QByteArray &eventType, void *message,
+                    QT_NATIVE_EVENT_RESULT_TYPE *result) override;
+
     protected:
         void winIdChanged() override;
         bool windowAttributeChanged(const QString &key, const QVariant &attribute,
diff --git a/src/core/kernel/nativeeventfilter.cpp b/src/core/kernel/nativeeventfilter.cpp
index be5666b..5ad57d1 100644
--- a/src/core/kernel/nativeeventfilter.cpp
+++ b/src/core/kernel/nativeeventfilter.cpp
@@ -30,6 +30,10 @@
         return false;
     }
 
+    void NativeEventDispatcher::resume(const QByteArray &eventType, void *message,
+                                       QT_NATIVE_EVENT_RESULT_TYPE *result) {
+    }
+
     void NativeEventDispatcher::installNativeEventFilter(NativeEventFilter *filter) {
         if (!filter || filter->m_dispatcher)
             return;
diff --git a/src/core/kernel/nativeeventfilter_p.h b/src/core/kernel/nativeeventfilter_p.h
index 8b50979..3e45722 100644
--- a/src/core/kernel/nativeeventfilter_p.h
+++ b/src/core/kernel/nativeeventfilter_p.h
@@ -25,6 +25,9 @@
         virtual bool dispatch(const QByteArray &eventType, void *message,
                               QT_NATIVE_EVENT_RESULT_TYPE *result);
 
+        virtual void resume(const QByteArray &eventType, void *message,
+                            QT_NATIVE_EVENT_RESULT_TYPE *result);
+
     public:
         void installNativeEventFilter(NativeEventFilter *filter);
         void removeNativeEventFilter(NativeEventFilter *filter);
diff --git a/src/core/shared/qwkwindowsextra_p.h b/src/core/shared/qwkwindowsextra_p.h
index df97727..ede119c 100644
--- a/src/core/shared/qwkwindowsextra_p.h
+++ b/src/core/shared/qwkwindowsextra_p.h
@@ -406,9 +406,8 @@
                                                          _DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1,
                                                          &enabled, sizeof(enabled)))) {
             return enabled;
-        } else {
-            return false;
         }
+        return false;
     }
 
     static inline QColor getAccentColor() {
diff --git a/src/widgets/widgetwindowagent_win.cpp b/src/widgets/widgetwindowagent_win.cpp
index 7901bac..173304f 100644
--- a/src/widgets/widgetwindowagent_win.cpp
+++ b/src/widgets/widgetwindowagent_win.cpp
@@ -22,16 +22,21 @@
             updateGeometry();
         }
 
+        inline bool isNormalWindow() const {
+            return widget->windowState() &
+                   (Qt::WindowMinimized | Qt::WindowMaximized | Qt::WindowFullScreen);
+        }
+
         void updateGeometry() {
-            if (widget->windowState() & (Qt::WindowMaximized | Qt::WindowFullScreen)) {
-                widget->setContentsMargins({});
-            } else {
+            if (isNormalWindow()) {
                 widget->setContentsMargins({
                     0,
                     ctx->property("borderThickness").toInt(),
                     0,
                     0,
                 });
+            } else {
+                widget->setContentsMargins({});
             }
         }
 
@@ -62,6 +67,16 @@
                     break;
                 }
 
+                case WM_PAINT: {
+                    // Let Qt paint first
+                    m_dispatcher->resume(eventType, message, result);
+
+                    // Upon receiving the WM_PAINT message, Qt will redraw the entire view, and we
+                    // must wait for it to finish redrawing before drawing this top border area
+                    ctx->virtual_hook(AbstractWindowContext::DrawWindows10BorderHook2, nullptr);
+                    return true;
+                }
+
                 default:
                     break;
             }
@@ -71,11 +86,7 @@
         bool eventFilter(QObject *obj, QEvent *event) override {
             Q_UNUSED(obj)
             switch (event->type()) {
-                case QEvent::Paint: {
-                    if (widget->windowState() &
-                        (Qt::WindowMinimized | Qt::WindowMaximized | Qt::WindowFullScreen))
-                        break;
-
+                case QEvent::UpdateRequest: {
                     // Friend class helping to call `event`
                     class HackedWidget : public QWidget {
                     public:
@@ -85,18 +96,10 @@
                     // Let the widget paint first
                     static_cast<HackedWidget *>(widget)->event(event);
 
-                    // Draw border
-                    auto paintEvent = static_cast<QPaintEvent *>(event);
-                    auto rect = paintEvent->rect();
-                    auto region = paintEvent->region();
-
-                    QPainter painter(widget);
-                    void *args[] = {
-                        &painter,
-                        &rect,
-                        &region,
-                    };
-                    ctx->virtual_hook(AbstractWindowContext::DrawWindows10BorderHook, args);
+                    // Due to the timer or user action, Qt will redraw some regions spontaneously,
+                    // even if there is no WM_PAINT message, we must wait for it to finish redrawing
+                    // and then update the upper border area
+                    ctx->virtual_hook(AbstractWindowContext::DrawWindows10BorderHook2, nullptr);
                     return true;
                 }
 

--
Gitblit v1.9.1