From 29901fc2a97eedd3c914f807d1819c9ea7e69973 Mon Sep 17 00:00:00 2001 From: Sine Striker <trueful@163.com> Date: 周一, 25 12月 2023 17:53:34 +0800 Subject: [PATCH] Optimize Windows 10 border handling --- src/core/contexts/win32windowcontext.cpp | 337 +++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 files changed, 307 insertions(+), 30 deletions(-) diff --git a/src/core/contexts/win32windowcontext.cpp b/src/core/contexts/win32windowcontext.cpp index 5575218..6dec43f 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> @@ -20,6 +21,8 @@ #else # include <QtGui/qpa/qplatformwindow_p.h> #endif + +#include <QWKCore/qwkconfig.h> #include "qwkglobal_p.h" #include "qwkwindowsextra_p.h" @@ -46,6 +49,21 @@ // Original Qt window proc function static WNDPROC g_qtWindowProc = nullptr; + + static inline bool +#if !QWINDOWKIT_CONFIG(ENABLE_WINDOWS_SYSTEM_BORDER) + constexpr +#endif + + isSystemBorderEnabled() { + return +#if QWINDOWKIT_CONFIG(ENABLE_WINDOWS_SYSTEM_BORDER) + isWin10OrGreater() +#else + false +#endif + ; + } static inline void triggerFrameChange(HWND hwnd) { ::SetWindowPos(hwnd, nullptr, 0, 0, 0, 0, @@ -546,7 +564,7 @@ static inline void addManagedWindow(QWindow *window, HWND hWnd, Win32WindowContext *ctx) { const auto margins = [hWnd]() -> QMargins { const auto titleBarHeight = int(getTitleBarHeight(hWnd)); - if (isWin10OrGreater()) { + if (isSystemBorderEnabled()) { return {0, -titleBarHeight, 0, 0}; } else { const auto frameSize = int(getResizeBorderThickness(hWnd)); @@ -641,6 +659,7 @@ } case DrawWindows10BorderHook: { +#if QWINDOWKIT_CONFIG(ENABLE_WINDOWS_SYSTEM_BORDER) if (!windowId) return; @@ -651,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)) { @@ -671,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); @@ -681,20 +700,78 @@ }); 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; + } + + // case AbstractWindowContext::DrawWindows10BackgroundHook: { + // #if QWINDOWKIT_CONFIG(ENABLE_WINDOWS_SYSTEM_BORDER) + // if (!m_windowHandle) + // return; + // + // auto hWnd = reinterpret_cast<HWND>(windowId); + // HDC hdc = ::GetDC(hWnd); + // RECT windowRect{}; + // ::GetClientRect(hWnd, &windowRect); + // RECT rcRest = { + // 0, + // int(getWindowFrameBorderThickness(hWnd)), + // RECT_WIDTH(windowRect), + // RECT_HEIGHT(windowRect), + // }; + // HBRUSH blueBrush = ::CreateSolidBrush(RGB(0, 0, 255)); + // + // // To hide the original title bar, we have to paint on top of it + // with + // // the alpha component set to 255. This is a hack to do it with + // GDI. + // // See NonClientIslandWindow::_UpdateFrameMargins for more + // information. HDC opaqueDc; BP_PAINTPARAMS params = + // {sizeof(params), BPPF_NOCLIP | BPPF_ERASE}; auto buf = + // BeginBufferedPaint(hdc, &rcRest, BPBF_TOPDOWNDIB, ¶ms, + // &opaqueDc); if (!buf || !opaqueDc) { + // return; + // } + // + // ::FillRect(opaqueDc, &rcRest, blueBrush); + // ::BufferedPaintSetAlpha(buf, nullptr, 255); + // ::EndBufferedPaint(buf, TRUE); + // + // ::DeleteObject(blueBrush); + // ::ReleaseDC(hWnd, hdc); + // #endif + // return; + // } default: break; } AbstractWindowContext::virtual_hook(id, data); - } - - bool Win32WindowContext::needBorderPainter() const { - return isWin10OrGreater() && !isWin11OrGreater(); - } - - int Win32WindowContext::borderThickness() const { - return getWindowFrameBorderThickness(reinterpret_cast<HWND>(windowId)); } void Win32WindowContext::winIdChanged() { @@ -712,15 +789,19 @@ auto winId = m_windowHandle->winId(); auto hWnd = reinterpret_cast<HWND>(winId); -#if QT_VERSION < QT_VERSION_CHECK(6, 5, 0) - for (const auto attr : { - _DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1, - _DWMWA_USE_IMMERSIVE_DARK_MODE, - }) { - const BOOL enable = TRUE; - DynamicApis::instance().pDwmSetWindowAttribute(hWnd, attr, &enable, sizeof(enable)); + if (!isSystemBorderEnabled()) { + setWindowAttribute("extra-margins", true); } + + { + auto style = ::GetWindowLongPtrW(hWnd, GWL_STYLE); +#if QWINDOWKIT_CONFIG(ENABLE_WINDOWS_SYSTEM_BORDER) + ::SetWindowLongPtrW(hWnd, GWL_STYLE, style & (~WS_SYSMENU)); +#else + ::SetWindowLongPtrW(hWnd, GWL_STYLE, + (style | WS_THICKFRAME | WS_CAPTION) & (~WS_SYSMENU)); #endif + } // Add managed window addManagedWindow(m_windowHandle, hWnd, this); @@ -774,12 +855,201 @@ msg.wParam = wParam; msg.lParam = lParam; QT_NATIVE_EVENT_RESULT_TYPE res = 0; - if (dispatch(QByteArrayLiteral("windows_generic_MSG"), &msg, &res)) { + if (nativeDispatch(QByteArrayLiteral("windows_generic_MSG"), &msg, &res)) { *result = LRESULT(res); return true; } } return false; // Not handled + } + + bool Win32WindowContext::windowAttributeChanged(const QString &key, const QVariant &attribute, + const QVariant &oldAttribute) { + const auto hwnd = reinterpret_cast<HWND>(m_windowHandle->winId()); + const DynamicApis &apis = DynamicApis::instance(); + static constexpr const MARGINS defaultEmptyMargins = {0, 0, 0, 0}; + static constexpr const MARGINS defaultExtraMargins = {1, 1, 1, 1}; + static constexpr const MARGINS extendedMargins = {-1, -1, -1, -1}; + if (key == QStringLiteral("extra-margins")) { + if (isWin11OrGreater()) + return false; + hasExtraMargins = attribute.toBool(); + DynamicApis::instance().pDwmExtendFrameIntoClientArea( + hwnd, hasExtraMargins ? &defaultExtraMargins : &defaultEmptyMargins); + return true; + } + + if (key == QStringLiteral("dark-mode")) { + if (!isWin101809OrGreater()) { + return false; + } + + 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, + }) { + apis.pDwmSetWindowAttribute(hwnd, attr, &enable, sizeof(enable)); + } + + apis.pFlushMenuThemes(); + return true; + } + + // For Win11 or later + static const auto &defaultMargins = + isSystemBorderEnabled() ? defaultExtraMargins : defaultEmptyMargins; + if (key == QStringLiteral("mica")) { + if (!isWin11OrGreater()) { + return false; + } + if (attribute.toBool()) { + // We need to extend the window frame into the whole client area to be able + // to see the blurred window background. + apis.pDwmExtendFrameIntoClientArea(hwnd, &extendedMargins); + if (isWin1122H2OrGreater()) { + // Use official DWM API to enable Mica, available since Windows 11 22H2 + // (10.0.22621). + const _DWM_SYSTEMBACKDROP_TYPE backdropType = _DWMSBT_MAINWINDOW; + apis.pDwmSetWindowAttribute(hwnd, _DWMWA_SYSTEMBACKDROP_TYPE, &backdropType, + sizeof(backdropType)); + } else { + // Use undocumented DWM API to enable Mica, available since Windows 11 + // (10.0.22000). + const BOOL enable = TRUE; + apis.pDwmSetWindowAttribute(hwnd, _DWMWA_MICA_EFFECT, &enable, sizeof(enable)); + } + } else { + if (isWin1122H2OrGreater()) { + const _DWM_SYSTEMBACKDROP_TYPE backdropType = _DWMSBT_AUTO; + apis.pDwmSetWindowAttribute(hwnd, _DWMWA_SYSTEMBACKDROP_TYPE, &backdropType, + sizeof(backdropType)); + } else { + const BOOL enable = FALSE; + apis.pDwmSetWindowAttribute(hwnd, _DWMWA_MICA_EFFECT, &enable, sizeof(enable)); + } + apis.pDwmExtendFrameIntoClientArea(hwnd, &defaultMargins); + } + return true; + } + + if (key == QStringLiteral("mica-alt")) { + if (!isWin1122H2OrGreater()) { + return false; + } + if (attribute.toBool()) { + // We need to extend the window frame into the whole client area to be able + // to see the blurred window background. + apis.pDwmExtendFrameIntoClientArea(hwnd, &extendedMargins); + // Use official DWM API to enable Mica Alt, available since Windows 11 22H2 + // (10.0.22621). + const _DWM_SYSTEMBACKDROP_TYPE backdropType = _DWMSBT_TABBEDWINDOW; + apis.pDwmSetWindowAttribute(hwnd, _DWMWA_SYSTEMBACKDROP_TYPE, &backdropType, + sizeof(backdropType)); + } else { + const _DWM_SYSTEMBACKDROP_TYPE backdropType = _DWMSBT_AUTO; + apis.pDwmSetWindowAttribute(hwnd, _DWMWA_SYSTEMBACKDROP_TYPE, &backdropType, + sizeof(backdropType)); + apis.pDwmExtendFrameIntoClientArea(hwnd, &defaultMargins); + } + return true; + } + + if (key == QStringLiteral("acrylic-material")) { + if (!isWin11OrGreater()) { + return false; + } + if (attribute.toBool()) { + // We need to extend the window frame into the whole client area to be able + // to see the blurred window background. + apis.pDwmExtendFrameIntoClientArea(hwnd, &extendedMargins); + + const _DWM_SYSTEMBACKDROP_TYPE backdropType = _DWMSBT_TRANSIENTWINDOW; + apis.pDwmSetWindowAttribute(hwnd, _DWMWA_SYSTEMBACKDROP_TYPE, &backdropType, + sizeof(backdropType)); + + // PRIVATE API REFERENCE: + // QColor gradientColor = {}; + // ACCENT_POLICY policy{}; + // policy.dwAccentState = ACCENT_ENABLE_ACRYLICBLURBEHIND; + // policy.dwAccentFlags = ACCENT_ENABLE_ACRYLIC_WITH_LUMINOSITY; + // // This API expects the #AABBGGRR format. + // policy.dwGradientColor = + // DWORD(qRgba(gradientColor.blue(), gradientColor.green(), + // gradientColor.red(), gradientColor.alpha())); + // WINDOWCOMPOSITIONATTRIBDATA wcad{}; + // wcad.Attrib = WCA_ACCENT_POLICY; + // wcad.pvData = &policy; + // wcad.cbData = sizeof(policy); + // apis.pSetWindowCompositionAttribute(hwnd, &wcad); + } else { + const _DWM_SYSTEMBACKDROP_TYPE backdropType = _DWMSBT_AUTO; + apis.pDwmSetWindowAttribute(hwnd, _DWMWA_SYSTEMBACKDROP_TYPE, &backdropType, + sizeof(backdropType)); + + // PRIVATE API REFERENCE: + // ACCENT_POLICY policy{}; + // policy.dwAccentState = ACCENT_DISABLED; + // policy.dwAccentFlags = ACCENT_NONE; + // WINDOWCOMPOSITIONATTRIBDATA wcad{}; + // wcad.Attrib = WCA_ACCENT_POLICY; + // wcad.pvData = &policy; + // wcad.cbData = sizeof(policy); + // apis.pSetWindowCompositionAttribute(hwnd, &wcad); + + apis.pDwmExtendFrameIntoClientArea(hwnd, &defaultMargins); + } + return true; + } + + if (key == QStringLiteral("dwm-blur")) { + // TODO: Optimize + // Currently not available!!! + if (attribute.toBool()) { + // We need to extend the window frame into the whole client area to be able + // to see the blurred window background. + apis.pDwmExtendFrameIntoClientArea(hwnd, &extendedMargins); + if (isWin8OrGreater()) { + ACCENT_POLICY policy{}; + policy.dwAccentState = ACCENT_ENABLE_BLURBEHIND; + policy.dwAccentFlags = ACCENT_NONE; + WINDOWCOMPOSITIONATTRIBDATA wcad{}; + wcad.Attrib = WCA_ACCENT_POLICY; + wcad.pvData = &policy; + wcad.cbData = sizeof(policy); + apis.pSetWindowCompositionAttribute(hwnd, &wcad); + } else { + DWM_BLURBEHIND bb{}; + bb.fEnable = TRUE; + bb.dwFlags = DWM_BB_ENABLE; + apis.pDwmEnableBlurBehindWindow(hwnd, &bb); + } + } else { + if (isWin8OrGreater()) { + ACCENT_POLICY policy{}; + policy.dwAccentState = ACCENT_DISABLED; + policy.dwAccentFlags = ACCENT_NONE; + WINDOWCOMPOSITIONATTRIBDATA wcad{}; + wcad.Attrib = WCA_ACCENT_POLICY; + wcad.pvData = &policy; + wcad.cbData = sizeof(policy); + apis.pSetWindowCompositionAttribute(hwnd, &wcad); + } else { + DWM_BLURBEHIND bb{}; + bb.fEnable = FALSE; + bb.dwFlags = DWM_BB_ENABLE; + apis.pDwmEnableBlurBehindWindow(hwnd, &bb); + } + apis.pDwmExtendFrameIntoClientArea(hwnd, &defaultMargins); + } + return true; + } + return false; } QWK_USED static constexpr const struct { @@ -1307,7 +1577,7 @@ int frameSize = getResizeBorderThickness(hWnd); bool isTop = (nativeLocalPos.y < frameSize); - if (isWin10OrGreater()) { + if (isSystemBorderEnabled()) { // This will handle the left, right and bottom parts of the frame // because we didn't change them. LRESULT originalHitTestResult = ::DefWindowProcW(hWnd, WM_NCHITTEST, 0, lParam); @@ -1435,7 +1705,7 @@ break; } - if (!isWin10OrGreater()) { + if (!isSystemBorderEnabled()) { switch (message) { case WM_NCUAHDRAWCAPTION: case WM_NCUAHDRAWFRAME: { @@ -1467,11 +1737,7 @@ // window activation state change. *result = ::DefWindowProcW(hWnd, WM_NCACTIVATE, wParam, -1); } else { - if (wParam) { - *result = FALSE; - } else { - *result = TRUE; - } + *result = TRUE; } return true; } @@ -1590,7 +1856,7 @@ // and align it with the upper-left corner of our new client area". const auto clientRect = wParam ? &(reinterpret_cast<LPNCCALCSIZE_PARAMS>(lParam))->rgrc[0] : reinterpret_cast<LPRECT>(lParam); - if (isWin10OrGreater()) { + if (isSystemBorderEnabled()) { // Store the original top margin before the default window procedure applies the // default frame. const LONG originalTop = clientRect->top; @@ -1617,6 +1883,7 @@ // technique to bring the top border back. clientRect->top = originalTop; } + const bool max = IsMaximized(hWnd); const bool full = isFullScreen(hWnd); // We don't need this correction when we're fullscreen. We will @@ -1631,7 +1898,7 @@ // a window when it's maximized unless you restore it). const quint32 frameSize = getResizeBorderThickness(hWnd); clientRect->top += frameSize; - if (!isWin10OrGreater()) { + if (!isSystemBorderEnabled()) { clientRect->bottom -= frameSize; clientRect->left += frameSize; clientRect->right -= frameSize; @@ -1734,10 +2001,11 @@ const auto getNativeGlobalPosFromKeyboard = [hWnd]() -> POINT { const bool maxOrFull = IsMaximized(hWnd) || isFullScreen(hWnd); const quint32 frameSize = getResizeBorderThickness(hWnd); - const quint32 horizontalOffset = ((maxOrFull || !isWin10OrGreater()) ? 0 : frameSize); + const quint32 horizontalOffset = + ((maxOrFull || !isSystemBorderEnabled()) ? 0 : frameSize); const auto verticalOffset = [hWnd, maxOrFull, frameSize]() -> quint32 { const quint32 titleBarHeight = getTitleBarHeight(hWnd); - if (!isWin10OrGreater()) { + if (!isSystemBorderEnabled()) { return titleBarHeight; } if (isWin11OrGreater()) { @@ -1814,4 +2082,13 @@ return false; } + bool Win32WindowContext::needBorderPainter() const { + Q_UNUSED(this) + return isSystemBorderEnabled() && !isWin11OrGreater(); + } + + int Win32WindowContext::borderThickness() const { + return int(getWindowFrameBorderThickness(reinterpret_cast<HWND>(windowId))); + } + } -- Gitblit v1.9.1