src/core/CMakeLists.txt | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/core/shared/windows10borderhandler_p.h | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/quick/quickwindowagent_win.cpp | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/widgets/widgetwindowagent_win.cpp | ●●●●● 补丁 | 查看 | 原始文档 | 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, ®ion, }; 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(); } 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() {