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, - ®ion, - }; - 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