From 013f2e845fd3af9737135330151b0e817f4ed533 Mon Sep 17 00:00:00 2001 From: Yuhang Zhao Date: Mon, 14 Aug 2023 11:02:17 +0800 Subject: [PATCH] win: minor improvement --- include/FramelessHelper/Core/utils.h | 5 +- src/core/framelesshelper_win.cpp | 31 +++++++++- src/core/utils.cpp | 41 +++++++++++++ src/core/utils_win.cpp | 91 ++++++++++++++++++++++++++-- 4 files changed, 160 insertions(+), 8 deletions(-) diff --git a/include/FramelessHelper/Core/utils.h b/include/FramelessHelper/Core/utils.h index 7a8aeab1..6e9d2070 100644 --- a/include/FramelessHelper/Core/utils.h +++ b/include/FramelessHelper/Core/utils.h @@ -87,6 +87,8 @@ FRAMELESSHELPER_CORE_API void registerThemeChangeNotification(); [[nodiscard]] FRAMELESSHELPER_CORE_API bool isValidGeometry(const QRect &rect); [[nodiscard]] FRAMELESSHELPER_CORE_API QColor getAccentColor(); [[nodiscard]] FRAMELESSHELPER_CORE_API quint32 defaultScreenDpi(); +[[nodiscard]] FRAMELESSHELPER_CORE_API bool isWindowAccelerated(const QWindow *window); +[[nodiscard]] FRAMELESSHELPER_CORE_API bool isWindowTransparent(const QWindow *window); #ifdef Q_OS_WINDOWS [[nodiscard]] FRAMELESSHELPER_CORE_API bool isWindowsVersionOrGreater(const Global::WindowsVersion version); @@ -101,7 +103,7 @@ FRAMELESSHELPER_CORE_API void syncWmPaintWithDwm(); FRAMELESSHELPER_CORE_API void showSystemMenu( const WId windowId, const QPoint &pos, const bool selectFirstEntry, const SystemParameters *params); -[[nodiscard]] FRAMELESSHELPER_CORE_API QColor getDwmColorizationColor(); +[[nodiscard]] FRAMELESSHELPER_CORE_API QColor getDwmColorizationColor(bool *opaque = nullptr, bool *ok = nullptr); [[nodiscard]] FRAMELESSHELPER_CORE_API Global::DwmColorizationArea getDwmColorizationArea(); [[nodiscard]] FRAMELESSHELPER_CORE_API bool isHighContrastModeEnabled(); [[nodiscard]] FRAMELESSHELPER_CORE_API quint32 getPrimaryScreenDpi(const bool horizontal); @@ -147,6 +149,7 @@ FRAMELESSHELPER_CORE_API void removeMicaWindow(const WId windowId); FRAMELESSHELPER_CORE_API void removeSysMenuHook(const WId windowId); FRAMELESSHELPER_CORE_API quint64 queryMouseButtonState(); FRAMELESSHELPER_CORE_API bool isValidWindow(const WId windowId, const bool checkVisible, const bool checkTopLevel); +[[nodiscard]] FRAMELESSHELPER_CORE_API bool updateFramebufferTransparency(const WId windowId); #endif // Q_OS_WINDOWS #ifdef Q_OS_LINUX diff --git a/src/core/framelesshelper_win.cpp b/src/core/framelesshelper_win.cpp index 80da240b..83f817a7 100644 --- a/src/core/framelesshelper_win.cpp +++ b/src/core/framelesshelper_win.cpp @@ -562,6 +562,7 @@ void FramelessHelperWin::addWindow(FramelessParamsConst params) qApp->installNativeEventFilter(g_framelessWin32HelperData()->nativeEventFilter.get()); } DEBUG.noquote() << "The DPI of window" << hwnd2str(windowId) << "is" << data.dpi; + const QWindow *window = params->getWindowHandle(); // Remove the bad window styles added by Qt (it's not that "bad" though). Utils::maybeFixupQtInternals(windowId); #if 0 @@ -571,7 +572,7 @@ void FramelessHelperWin::addWindow(FramelessParamsConst params) // otherwise we'll get lots of warning messages when we change the window // geometry, it will also affect the final window geometry because QPA will // always take it into account when setting window size and position. - Utils::updateInternalWindowFrameMargins(params->getWindowHandle(), true); + Utils::updateInternalWindowFrameMargins(const_cast(window), true); #endif // Tell DWM our preferred frame margin. Utils::updateWindowFrameMargins(windowId, false); @@ -581,6 +582,11 @@ void FramelessHelperWin::addWindow(FramelessParamsConst params) // Windows, which means only the top level windows can be scaled to the correct // size, we of course don't want such thing from happening. Utils::fixupChildWindowsDpiMessage(windowId); + if (Utils::isWindowAccelerated(window) && Utils::isWindowTransparent(window)) { + if (!Utils::updateFramebufferTransparency(windowId)) { + WARNING << "Failed to update the frame buffer transparency."; + } + } if (WindowsVersionHelper::isWin10RS1OrGreater()) { // Tell DWM we may need dark theme non-client area (title bar & frame border). FramelessHelper::Core::setApplicationOSThemeAware(); @@ -671,6 +677,7 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me } const FramelessWin32HelperData &data = it.value(); FramelessWin32HelperData &muData = it.value(); + const QWindow *window = data.params.getWindowHandle(); const bool frameBorderVisible = Utils::isWindowFrameBorderVisible(); const WPARAM wParam = msg->wParam; const LPARAM lParam = msg->lParam; @@ -1032,8 +1039,7 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me WARNING << Utils::getSystemErrorMessage(kScreenToClient); break; } - const QPoint qtScenePos = Utils::fromNativeLocalPosition( - data.params.getWindowHandle(), QPoint(nativeLocalPos.x, nativeLocalPos.y)); + const QPoint qtScenePos = Utils::fromNativeLocalPosition(window, QPoint(nativeLocalPos.x, nativeLocalPos.y)); const bool max = IsMaximized(hWnd); const bool full = Utils::isFullScreen(windowId); const int frameSizeY = Utils::getResizeBorderThickness(windowId, false, true); @@ -1255,6 +1261,18 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me } } break; #endif // (QT_VERSION < QT_VERSION_CHECK(6, 5, 1)) + case WM_SYSCOMMAND: { + const WPARAM filteredWParam = (wParam & 0xFFF0); + // When the window is fullscreened, don't enter screen saver or power + // down the monitor (only a suggestion to the OS, the OS can still ignore + // our request). + if ((filteredWParam == SC_SCREENSAVE) || (filteredWParam == SC_MONITORPOWER)) { + if (Utils::isFullScreen(windowId)) { + *result = 0; + return true; + } + } + } break; default: break; } @@ -1331,6 +1349,13 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me break; } } + if ((uMsg == WM_DWMCOMPOSITIONCHANGED) || (uMsg == WM_DWMCOLORIZATIONCOLORCHANGED)) { + if (Utils::isWindowAccelerated(window) && Utils::isWindowTransparent(window)) { + if (!Utils::updateFramebufferTransparency(windowId)) { + WARNING << "Failed to update the frame buffer transparency."; + } + } + } if (WindowsVersionHelper::isWin11OrGreater() && data.fallbackTitleBarWindowId) { switch (uMsg) { case WM_SIZE: // Sent to a window after its size has changed. diff --git a/src/core/utils.cpp b/src/core/utils.cpp index 3e81ff80..9388ec92 100644 --- a/src/core/utils.cpp +++ b/src/core/utils.cpp @@ -34,8 +34,11 @@ #include #include #include +#include +#include #ifndef FRAMELESSHELPER_CORE_NO_PRIVATE # include +# include #endif // FRAMELESSHELPER_CORE_NO_PRIVATE #if (QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)) # include @@ -587,4 +590,42 @@ QColor Utils::getAccentColor() #endif // (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)) } +bool Utils::isWindowAccelerated(const QWindow *window) +{ + Q_ASSERT(window); + if (!window) { + return false; + } + switch (window->surfaceType()) { + case QSurface::RasterGLSurface: +#ifdef FRAMELESSHELPER_CORE_NO_PRIVATE + return false; +#else + return qt_window_private(const_cast(window))->compositing; +#endif + case QSurface::OpenGLSurface: + case QSurface::VulkanSurface: + case QSurface::MetalSurface: + case QSurface::Direct3DSurface: + return true; + default: + break; + } + return false; +} + +bool Utils::isWindowTransparent(const QWindow *window) +{ + Q_ASSERT(window); + if (!window) { + return false; + } + // On most platforms, QWindow::format() will just return the + // user set format if there is one, otherwise it will return + // an invalid surface format. That means, most of the time + // the following check will not be useful. But since this is + // what the QPA code does, we just mirror it here. + return window->format().hasAlpha(); +} + FRAMELESSHELPER_END_NAMESPACE diff --git a/src/core/utils_win.cpp b/src/core/utils_win.cpp index b3e519ae..12a60d2c 100644 --- a/src/core/utils_win.cpp +++ b/src/core/utils_win.cpp @@ -776,7 +776,7 @@ QString Utils::getSystemErrorMessage(const QString &function) return getSystemErrorMessageImpl(function, code); } -QColor Utils::getDwmColorizationColor() +QColor Utils::getDwmColorizationColor(bool *opaque, bool *ok) { const auto resultFromRegistry = []() -> QColor { const RegistryKey registry(RegistryRootKey::CurrentUser, dwmRegistryKey()); @@ -790,15 +790,27 @@ QColor Utils::getDwmColorizationColor() return QColor::fromRgba(qvariant_cast(value)); }; if (!API_DWM_AVAILABLE(DwmGetColorizationColor)) { + if (ok) { + *ok = false; + } return resultFromRegistry(); } DWORD color = 0; - BOOL opaque = FALSE; - const HRESULT hr = API_CALL_FUNCTION(dwmapi, DwmGetColorizationColor, &color, &opaque); + BOOL bOpaque = FALSE; + const HRESULT hr = API_CALL_FUNCTION(dwmapi, DwmGetColorizationColor, &color, &bOpaque); if (FAILED(hr)) { WARNING << getSystemErrorMessageImpl(kDwmGetColorizationColor, hr); + if (ok) { + *ok = false; + } return resultFromRegistry(); } + if (opaque) { + *opaque = (bOpaque != FALSE); + } + if (ok) { + *ok = true; + } return QColor::fromRgba(color); } @@ -1357,7 +1369,7 @@ void Utils::maybeFixupQtInternals(const WId windowId) // and this will also break the normal functionalities for our windows, so we do the // correction here unconditionally. static constexpr const DWORD badWindowStyle = WS_POPUP; - static constexpr const DWORD goodWindowStyle = WS_OVERLAPPEDWINDOW; + static constexpr const DWORD goodWindowStyle = (WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS); if ((windowStyle & badWindowStyle) || !(windowStyle & goodWindowStyle)) { SetLastError(ERROR_SUCCESS); if (SetWindowLongPtrW(hwnd, GWL_STYLE, ((windowStyle & ~badWindowStyle) | goodWindowStyle)) == 0) { @@ -1367,6 +1379,22 @@ void Utils::maybeFixupQtInternals(const WId windowId) } } } + SetLastError(ERROR_SUCCESS); + const auto extendedWindowStyle = static_cast(GetWindowLongPtrW(hwnd, GWL_EXSTYLE)); + if (extendedWindowStyle == 0) { + WARNING << getSystemErrorMessage(kGetWindowLongPtrW); + } else { + static constexpr const DWORD badWindowStyle = (WS_EX_OVERLAPPEDWINDOW | WS_EX_STATICEDGE | WS_EX_DLGMODALFRAME | WS_EX_CONTEXTHELP); + static constexpr const DWORD goodWindowStyle = WS_EX_APPWINDOW; + if ((extendedWindowStyle & badWindowStyle) || !(extendedWindowStyle & goodWindowStyle)) { + SetLastError(ERROR_SUCCESS); + if (SetWindowLongPtrW(hwnd, GWL_EXSTYLE, ((extendedWindowStyle & ~badWindowStyle) | goodWindowStyle)) == 0) { + WARNING << getSystemErrorMessage(kSetWindowLongPtrW); + } else { + shouldUpdateFrame = true; + } + } + } if (shouldUpdateFrame) { triggerFrameChange(windowId); } @@ -2574,4 +2602,59 @@ bool Utils::isValidWindow(const WId windowId, const bool checkVisible, const boo return true; } +bool Utils::updateFramebufferTransparency(const WId windowId) +{ + Q_ASSERT(windowId); + if (!windowId) { + return false; + } + if (!API_DWM_AVAILABLE(DwmEnableBlurBehindWindow)) { + WARNING << "DwmEnableBlurBehindWindow() is not available on current platform."; + return false; + } + // DwmEnableBlurBehindWindow() won't be functional if DWM composition + // is not enabled, so we bail out early if this is the case. + if (!isDwmCompositionEnabled()) { + return false; + } + const auto hwnd = reinterpret_cast(windowId); + bool opaque = false; + bool ok = false; + std::ignore = getDwmColorizationColor(&opaque, &ok); + if (WindowsVersionHelper::isWin8OrGreater() || (ok && !opaque)) { +#if 0 // Windows QPA will always do this for us. + DWM_BLURBEHIND bb; + SecureZeroMemory(&bb, sizeof(bb)); + bb.dwFlags = (DWM_BB_ENABLE | DWM_BB_BLURREGION); + bb.hRgnBlur = CreateRectRgn(0, 0, -1, -1); + bb.fEnable = TRUE; + const HRESULT hr = API_CALL_FUNCTION(dwmapi, DwmEnableBlurBehindWindow, hwnd, &bb); + if (bb.hRgnBlur) { + if (DeleteObject(bb.hRgnBlur) == FALSE) { + WARNING << getSystemErrorMessage(kDeleteObject); + } + } + if (FAILED(hr)) { + WARNING << getSystemErrorMessageImpl(kDwmEnableBlurBehindWindow, hr); + return false; + } +#endif + } else { + // HACK: Disable framebuffer transparency on Windows 7 when the + // colorization color is opaque, because otherwise the window + // contents is blended additively with the previous frame instead + // of replacing it + DWM_BLURBEHIND bb; + SecureZeroMemory(&bb, sizeof(bb)); + bb.dwFlags = DWM_BB_ENABLE; + bb.fEnable = FALSE; + const HRESULT hr = API_CALL_FUNCTION(dwmapi, DwmEnableBlurBehindWindow, hwnd, &bb); + if (FAILED(hr)) { + WARNING << getSystemErrorMessageImpl(kDwmEnableBlurBehindWindow, hr); + return false; + } + } + return true; +} + FRAMELESSHELPER_END_NAMESPACE