Sine Striker
2024-02-25 40d73269371a9e2b142eb2be2bd2f70aff6346d2
Better workaround for win10 top border issue
3个文件已修改
1个文件已添加
319 ■■■■ 已修改文件
src/core/CMakeLists.txt 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/core/shared/windows10borderhandler_p.h 108 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/quick/quickwindowagent_win.cpp 112 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/widgets/widgetwindowagent_win.cpp 98 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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()
src/core/shared/windows10borderhandler_p.h
New file
@@ -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
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() {
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();
            // First update
            if (ctx->windowId()) {
                setupNecessaryAttributes();
            }
            WidgetBorderHandler::updateGeometry();
        }
        inline bool isNormalWindow() const {
            return !(widget->windowState() &
                     (Qt::WindowMinimized | Qt::WindowMaximized | Qt::WindowFullScreen));
        void updateGeometry() override {
            widget->setContentsMargins(isNormalWindow() ? QMargins(0, borderThickness(), 0, 0)
                                                        : QMargins());
        }
        inline void updateGeometry() {
            if (isNormalWindow()) {
                widget->setContentsMargins(
                    {0, ctx->windowAttribute(QStringLiteral("border-thickness")).toInt(), 0, 0});
            } else {
                widget->setContentsMargins({});
            }
        bool isWindowActive() const override {
            return widget->isActiveWindow();
        }
        inline void resumeWidgetEventAndDraw(QWidget *w, QEvent *event) {
        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() {