From 40d73269371a9e2b142eb2be2bd2f70aff6346d2 Mon Sep 17 00:00:00 2001
From: Sine Striker <trueful@163.com>
Date: 周日, 25 2月 2024 04:13:02 +0800
Subject: [PATCH] Better workaround for win10 top border issue

---
 src/widgets/widgetwindowagent_win.cpp      |  104 +++-------------
 src/core/CMakeLists.txt                    |    1 
 src/quick/quickwindowagent_win.cpp         |  112 ++++++------------
 src/core/shared/windows10borderhandler_p.h |  108 ++++++++++++++++++
 4 files changed, 171 insertions(+), 154 deletions(-)

diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index fb059bc..3849ced 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -29,6 +29,7 @@
         qwindowkit_windows.h
         qwindowkit_windows.cpp
         shared/qwkwindowsextra_p.h
+        shared/windows10borderhandler_p.h
     )
 elseif(APPLE)
 else()
diff --git a/src/core/shared/windows10borderhandler_p.h b/src/core/shared/windows10borderhandler_p.h
new file mode 100644
index 0000000..7fc9760
--- /dev/null
+++ b/src/core/shared/windows10borderhandler_p.h
@@ -0,0 +1,108 @@
+#ifndef WINDOWS10BORDERHANDLER_P_H
+#define WINDOWS10BORDERHANDLER_P_H
+
+#include <QtGui/QWindow>
+#include <QtGui/QMouseEvent>
+
+#include <QWKCore/qwindowkit_windows.h>
+#include <QWKCore/private/qwkglobal_p.h>
+#include <QWKCore/private/abstractwindowcontext_p.h>
+
+namespace QWK {
+
+    class Windows10BorderHandler : public NativeEventFilter, public SharedEventFilter {
+    public:
+        inline Windows10BorderHandler(AbstractWindowContext *ctx) : ctx(ctx) {
+            ctx->installNativeEventFilter(this);
+            ctx->installSharedEventFilter(this);
+        }
+
+        inline void setupNecessaryAttributes() {
+            // https://github.com/microsoft/terminal/blob/71a6f26e6ece656084e87de1a528c4a8072eeabd/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp#L940
+            // Must extend top frame to client area
+            static QVariant defaultMargins = QVariant::fromValue(QMargins(0, 1, 0, 0));
+            ctx->setWindowAttribute(QStringLiteral("extra-margins"), defaultMargins);
+
+            // Enable dark mode by default, otherwise the system borders are white
+            ctx->setWindowAttribute(QStringLiteral("dark-mode"), true);
+        }
+
+        inline bool isNormalWindow() const {
+            return !(ctx->window()->windowStates() &
+                     (Qt::WindowMinimized | Qt::WindowMaximized | Qt::WindowFullScreen));
+        }
+
+        inline void drawBorder() {
+            ctx->virtual_hook(AbstractWindowContext::DrawWindows10BorderHook2, nullptr);
+        }
+
+        inline int borderThickness() const {
+            return ctx->windowAttribute(QStringLiteral("border-thickness")).toInt();
+        }
+
+        inline void updateExtraMargins(bool windowActive) {
+            if (windowActive) {
+                // Restore margins when the window is active
+                static QVariant defaultMargins = QVariant::fromValue(QMargins(0, 1, 0, 0));
+                ctx->setWindowAttribute(QStringLiteral("extra-margins"), defaultMargins);
+                return;
+            }
+
+            // https://github.com/microsoft/terminal/blob/71a6f26e6ece656084e87de1a528c4a8072eeabd/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp#L904
+            // When the window is inactive, there is a transparency bug in the top
+            // border, and we need to extend the non-client area to the whole title
+            // bar.
+            QRect frame = ctx->windowAttribute(QStringLiteral("window-rect")).toRect();
+            QMargins margins{0, -frame.top(), 0, 0};
+            ctx->setWindowAttribute(QStringLiteral("extra-margins"), QVariant::fromValue(margins));
+        }
+
+        virtual void updateGeometry() = 0;
+
+        virtual bool isWindowActive() const {
+            return ctx->window()->isActive();
+        }
+
+    protected:
+        bool nativeEventFilter(const QByteArray &eventType, void *message,
+                               QT_NATIVE_EVENT_RESULT_TYPE *result) override {
+            Q_UNUSED(eventType)
+
+            const auto msg = static_cast<const MSG *>(message);
+            switch (msg->message) {
+                case WM_DPICHANGED: {
+                    updateGeometry();
+                    updateExtraMargins(isWindowActive());
+                    break;
+                }
+
+                case WM_ACTIVATE: {
+                    updateExtraMargins(LOWORD(msg->wParam) != WA_INACTIVE);
+                    break;
+                }
+
+                default:
+                    break;
+            }
+            return false;
+        }
+
+        bool sharedEventFilter(QObject *obj, QEvent *event) override {
+            Q_UNUSED(obj)
+
+            if (event->type() == QEvent::WinIdChange) {
+                if (ctx->windowId()) {
+                    setupNecessaryAttributes();
+                    updateGeometry();
+                }
+            }
+            return false;
+        }
+
+    protected:
+        AbstractWindowContext *ctx;
+    };
+
+}
+
+#endif // WINDOWS10BORDERHANDLER_P_H
diff --git a/src/quick/quickwindowagent_win.cpp b/src/quick/quickwindowagent_win.cpp
index a265bbb..011dae0 100644
--- a/src/quick/quickwindowagent_win.cpp
+++ b/src/quick/quickwindowagent_win.cpp
@@ -8,43 +8,51 @@
 #include <QtQuick/private/qquickitem_p.h>
 
 #include <QWKCore/qwindowkit_windows.h>
+#include <QWKCore/private/windows10borderhandler_p.h>
 
 namespace QWK {
 
 #if QWINDOWKIT_CONFIG(ENABLE_WINDOWS_SYSTEM_BORDERS)
-    // TODO: Find a way to draw native border
-    // We haven't found a way to place hooks in the Quick program and call the GDI API to draw
-    // the native border area so that we'll use the emulated drawn border for now.
 
-    class BorderItem : public QQuickPaintedItem,
-                       public NativeEventFilter,
-                       public SharedEventFilter {
+    class DeferredDeleteHook;
+
+    class BorderItem : public QQuickPaintedItem, public Windows10BorderHandler {
     public:
         explicit BorderItem(QQuickItem *parent, AbstractWindowContext *context);
         ~BorderItem() override;
 
-        inline bool isNormalWindow() const;
-
-        inline void updateGeometry();
+        void updateGeometry() override;
 
     public:
         void paint(QPainter *painter) override;
         void itemChange(ItemChange change, const ItemChangeData &data) override;
 
     protected:
-        bool nativeEventFilter(const QByteArray &eventType, void *message,
-                               QT_NATIVE_EVENT_RESULT_TYPE *result) override;
-
         bool sharedEventFilter(QObject *obj, QEvent *event) override;
 
-        AbstractWindowContext *context;
+        std::unique_ptr<DeferredDeleteHook> paintHook;
+    };
 
-    private:
-        void _q_windowActivityChanged();
+    class DeferredDeleteHook : public QObject {
+    public:
+        DeferredDeleteHook(BorderItem *item) : item(item) {
+        }
+
+        // Override deferred delete handler
+        bool event(QEvent *event) override {
+            if (event->type() == QEvent::DeferredDelete) {
+                item->drawBorder();
+                return true;
+            }
+            return QObject::event(event);
+        }
+
+        BorderItem *item;
     };
 
     BorderItem::BorderItem(QQuickItem *parent, AbstractWindowContext *context)
-        : QQuickPaintedItem(parent), context(context) {
+        : QQuickPaintedItem(parent), Windows10BorderHandler(context),
+          paintHook(std::make_unique<DeferredDeleteHook>(this)) {
         setAntialiasing(true);   // We need anti-aliasing to give us better result.
         setFillColor({});        // Will improve the performance a little bit.
         setOpaquePainting(true); // Will also improve the performance, we don't draw
@@ -59,35 +67,29 @@
         setZ(std::numeric_limits<qreal>::max()); // Make sure our fake border always above
                                                  // everything in the window.
 
-        context->installNativeEventFilter(this);
-        context->installSharedEventFilter(this);
-
-        connect(window(), &QQuickWindow::activeChanged, this,
-                &BorderItem::_q_windowActivityChanged);
-        updateGeometry();
+        // First update
+        if (context->windowId()) {
+            setupNecessaryAttributes();
+        }
+        BorderItem::updateGeometry();
     }
 
     BorderItem::~BorderItem() = default;
 
-    bool BorderItem::isNormalWindow() const {
-        return !(context->window()->windowStates() &
-                 (Qt::WindowMinimized | Qt::WindowMaximized | Qt::WindowFullScreen));
-    }
-
     void BorderItem::updateGeometry() {
-        setHeight(context->windowAttribute(QStringLiteral("border-thickness")).toInt());
+        setHeight(borderThickness());
         setVisible(isNormalWindow());
     }
 
     void BorderItem::paint(QPainter *painter) {
-        QRect rect(QPoint(0, 0), size().toSize());
-        QRegion region(rect);
-        void *args[] = {
-            painter,
-            &rect,
-            &region,
-        };
-        context->virtual_hook(AbstractWindowContext::DrawWindows10BorderHook, args);
+        Q_UNUSED(painter)
+
+        // https://github.com/qt/qtdeclarative/blob/cc04afbb382fd4b1f65173d71f44d3372c47b0e1/src/quick/scenegraph/qsgthreadedrenderloop.cpp#L551
+        // https://github.com/qt/qtdeclarative/blob/cc04afbb382fd4b1f65173d71f44d3372c47b0e1/src/quick/scenegraph/qsgthreadedrenderloop.cpp#L561
+        // QCoreApplication must process all DeferredDelay events right away after rendering, we can
+        // hook it by overriding a specifiy QObject's event handler and draw the native border in
+        // it.
+        paintHook->deleteLater();
     }
 
     void BorderItem::itemChange(ItemChange change, const ItemChangeData &data) {
@@ -103,42 +105,10 @@
         }
     }
 
-    bool BorderItem::nativeEventFilter(const QByteArray &eventType, void *message,
-                                       QT_NATIVE_EVENT_RESULT_TYPE *result) {
-        Q_UNUSED(eventType)
-
-        const auto msg = static_cast<const MSG *>(message);
-        switch (msg->message) {
-            case WM_THEMECHANGED:
-            case WM_SYSCOLORCHANGE:
-            case WM_DWMCOLORIZATIONCOLORCHANGED: {
-                update();
-                break;
-            }
-
-            case WM_SETTINGCHANGE: {
-                if (isImmersiveColorSetChange(msg->wParam, msg->lParam)) {
-                    update();
-                }
-                break;
-            }
-
-            default:
-                break;
-        }
-        return false;
-    }
-
     bool BorderItem::sharedEventFilter(QObject *obj, QEvent *event) {
         Q_UNUSED(obj)
 
         switch (event->type()) {
-            case QEvent::WinIdChange: {
-                if (auto winId = context->windowId()) {
-                    updateGeometry();
-                }
-                break;
-            }
             case QEvent::WindowStateChange: {
                 updateGeometry();
                 break;
@@ -146,11 +116,7 @@
             default:
                 break;
         }
-        return false;
-    }
-
-    void BorderItem::_q_windowActivityChanged() {
-        update();
+        return Windows10BorderHandler::sharedEventFilter(obj, event);
     }
 
     void QuickWindowAgentPrivate::setupWindows10BorderWorkaround() {
diff --git a/src/widgets/widgetwindowagent_win.cpp b/src/widgets/widgetwindowagent_win.cpp
index 791ed40..13af840 100644
--- a/src/widgets/widgetwindowagent_win.cpp
+++ b/src/widgets/widgetwindowagent_win.cpp
@@ -10,6 +10,7 @@
 
 #include <QWKCore/qwindowkit_windows.h>
 #include <QWKCore/private/qwkglobal_p.h>
+#include <QWKCore/private/windows10borderhandler_p.h>
 
 namespace QWK {
 
@@ -37,101 +38,49 @@
     // returns, because Qt calls BeginPaint() and EndPaint() itself. We should make sure that we
     // draw the top border between these two calls, otherwise some display exceptions may arise.
 
-    class WidgetBorderHandler : public QObject, public NativeEventFilter, public SharedEventFilter {
+    class WidgetBorderHandler : public QObject, public Windows10BorderHandler {
     public:
         explicit WidgetBorderHandler(QWidget *widget, AbstractWindowContext *ctx,
                                      QObject *parent = nullptr)
-            : QObject(parent), widget(widget), ctx(ctx) {
+            : QObject(parent), Windows10BorderHandler(ctx), widget(widget) {
             widget->installEventFilter(this);
 
-            // https://github.com/microsoft/terminal/blob/71a6f26e6ece656084e87de1a528c4a8072eeabd/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp#L940
-            // Must extend top frame to client area
-            static QVariant defaultMargins = QVariant::fromValue(QMargins(0, 1, 0, 0));
-            ctx->setWindowAttribute(QStringLiteral("extra-margins"), defaultMargins);
-
-            // Enable dark mode by default, otherwise the system borders are white
-            ctx->setWindowAttribute(QStringLiteral("dark-mode"), true);
-
-            ctx->installNativeEventFilter(this);
-            ctx->installSharedEventFilter(this);
-
-            updateGeometry();
-        }
-
-        inline bool isNormalWindow() const {
-            return !(widget->windowState() &
-                     (Qt::WindowMinimized | Qt::WindowMaximized | Qt::WindowFullScreen));
-        }
-
-        inline void updateGeometry() {
-            if (isNormalWindow()) {
-                widget->setContentsMargins(
-                    {0, ctx->windowAttribute(QStringLiteral("border-thickness")).toInt(), 0, 0});
-            } else {
-                widget->setContentsMargins({});
+            // First update
+            if (ctx->windowId()) {
+                setupNecessaryAttributes();
             }
+            WidgetBorderHandler::updateGeometry();
         }
 
-        inline void resumeWidgetEventAndDraw(QWidget *w, QEvent *event) {
+        void updateGeometry() override {
+            widget->setContentsMargins(isNormalWindow() ? QMargins(0, borderThickness(), 0, 0)
+                                                        : QMargins());
+        }
+
+        bool isWindowActive() const override {
+            return widget->isActiveWindow();
+        }
+
+        inline void forwardEventToWidgetAndDraw(QWidget *w, QEvent *event) {
             // Let the widget paint first
             static_cast<QObject *>(w)->event(event);
 
             // Due to the timer or user action, Qt will repaint some regions spontaneously,
             // even if there is no WM_PAINT message, we must wait for it to finish painting
             // and then update the top border area.
-            ctx->virtual_hook(AbstractWindowContext::DrawWindows10BorderHook2, nullptr);
+            drawBorder();
         }
 
-        inline void resumeWindowEventAndDraw(QWindow *window, QEvent *event) {
+        inline void forwardEventToWindowAndDraw(QWindow *window, QEvent *event) {
             // Let Qt paint first
             static_cast<QObject *>(window)->event(event);
 
             // Upon receiving the WM_PAINT message, Qt will repaint the entire view, and we
             // must wait for it to finish painting before drawing this top border area.
-            ctx->virtual_hook(AbstractWindowContext::DrawWindows10BorderHook2, nullptr);
-        }
-
-        inline void updateExtraMargins(bool windowActive) {
-            if (windowActive) {
-                // Restore margins when the window is active
-                static QVariant defaultMargins = QVariant::fromValue(QMargins(0, 1, 0, 0));
-                ctx->setWindowAttribute(QStringLiteral("extra-margins"), defaultMargins);
-                return;
-            }
-
-            // https://github.com/microsoft/terminal/blob/71a6f26e6ece656084e87de1a528c4a8072eeabd/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp#L904
-            // When the window is inactive, there is a transparency bug in the top
-            // border, and we need to extend the non-client area to the whole title
-            // bar.
-            QRect frame = ctx->windowAttribute(QStringLiteral("window-rect")).toRect();
-            QMargins margins{0, -frame.top(), 0, 0};
-            ctx->setWindowAttribute(QStringLiteral("extra-margins"), QVariant::fromValue(margins));
+            drawBorder();
         }
 
     protected:
-        bool nativeEventFilter(const QByteArray &eventType, void *message,
-                               QT_NATIVE_EVENT_RESULT_TYPE *result) override {
-            Q_UNUSED(eventType)
-
-            const auto msg = static_cast<const MSG *>(message);
-            switch (msg->message) {
-                case WM_DPICHANGED: {
-                    updateGeometry();
-                    updateExtraMargins(widget->isActiveWindow());
-                    break;
-                }
-
-                case WM_ACTIVATE: {
-                    updateExtraMargins(LOWORD(msg->wParam) != WA_INACTIVE);
-                    break;
-                }
-
-                default:
-                    break;
-            }
-            return false;
-        }
-
         bool sharedEventFilter(QObject *obj, QEvent *event) override {
             Q_UNUSED(obj)
 
@@ -147,21 +96,15 @@
                     // simply ignore it.
                     auto ee = static_cast<QExposeEvent *>(event);
                     if (window->isExposed() && isNormalWindow() && !ee->region().isNull()) {
-                        resumeWindowEventAndDraw(window, event);
+                        forwardEventToWindowAndDraw(window, event);
                         return true;
-                    }
-                    break;
-                }
-                case QEvent::WinIdChange: {
-                    if (auto winId = ctx->windowId()) {
-                        updateGeometry();
                     }
                     break;
                 }
                 default:
                     break;
             }
-            return false;
+            return Windows10BorderHandler::sharedEventFilter(obj, event);
         }
 
         bool eventFilter(QObject *obj, QEvent *event) override {
@@ -171,7 +114,7 @@
                 case QEvent::UpdateRequest: {
                     if (!isNormalWindow())
                         break;
-                    resumeWidgetEventAndDraw(widget, event);
+                    forwardEventToWidgetAndDraw(widget, event);
                     return true;
                 }
 
@@ -193,7 +136,6 @@
         }
 
         QWidget *widget;
-        AbstractWindowContext *ctx;
     };
 
     void WidgetWindowAgentPrivate::setupWindows10BorderWorkaround() {

--
Gitblit v1.9.1