From b9f5cf79c032691cab3ee3d97c08d1781d84fdce Mon Sep 17 00:00:00 2001 From: Yuhang Zhao <2546789017@qq.com> Date: Tue, 11 Oct 2022 16:17:04 +0800 Subject: [PATCH] win32: add support for dark theme menu Signed-off-by: Yuhang Zhao <2546789017@qq.com> --- examples/dialog/dialog.cpp | 2 +- .../Core/framelesshelper_windows.h | 40 +- .../Core/framelesshelpercore_global.h | 13 +- .../Core/private/sysapiloader_p.h | 1 + include/FramelessHelper/Core/utils.h | 2 +- src/core/cmakehelper.cmake | 4 +- src/core/framelesshelper_qt.cpp | 3 - src/core/framelesshelper_win.cpp | 12 +- src/core/sysapiloader.cpp | 20 +- src/core/utils.cpp | 6 +- src/core/utils_win.cpp | 417 +++++++++++++++--- tools/dpitester/CMakeLists.txt | 4 +- 12 files changed, 419 insertions(+), 105 deletions(-) diff --git a/examples/dialog/dialog.cpp b/examples/dialog/dialog.cpp index 582fb42..adfec8f 100644 --- a/examples/dialog/dialog.cpp +++ b/examples/dialog/dialog.cpp @@ -134,7 +134,7 @@ void Dialog::setupUi() // with making the window un-resizable: we still want the window be able to resize // programatically, but we also want the user not able to resize the window manually. // So apparently we can't use QWidget::setFixedWidth/Height/Size() here. - FramelessWidgetsHelperPrivate::get(helper)->setProperty(FRAMELESSHELPER_BYTEARRAY_LITERAL("FRAMELESSHELPER_DONT_OVERRIDE_CURSOR"), true); + FramelessWidgetsHelperPrivate::get(helper)->setProperty(kDontOverrideCursorVar, true); connect(helper, &FramelessWidgetsHelper::ready, this, [this, helper](){ const QScopedPointer settings(appConfigFile()); const QByteArray data = settings->value(kIniKeyPath).toByteArray(); diff --git a/include/FramelessHelper/Core/framelesshelper_windows.h b/include/FramelessHelper/Core/framelesshelper_windows.h index a06a34c..e4ef951 100644 --- a/include/FramelessHelper/Core/framelesshelper_windows.h +++ b/include/FramelessHelper/Core/framelesshelper_windows.h @@ -58,8 +58,8 @@ # define _WIN32_WINNT_WIN10 0x0A00 #endif -#ifndef NTDDI_WIN10_CO -# define NTDDI_WIN10_CO 0x0A00000B +#ifndef NTDDI_WIN10_NI +# define NTDDI_WIN10_NI 0x0A00000C #endif #ifndef WINVER @@ -71,7 +71,7 @@ #endif #ifndef NTDDI_VERSION -# define NTDDI_VERSION NTDDI_WIN10_CO +# define NTDDI_VERSION NTDDI_WIN10_NI #endif #include @@ -375,9 +375,6 @@ using PWINDOWCOMPOSITIONATTRIBDATA = WINDOWCOMPOSITIONATTRIBDATA *; using NPWINDOWCOMPOSITIONATTRIBDATA = WINDOWCOMPOSITIONATTRIBDATA NEAR *; using LPWINDOWCOMPOSITIONATTRIBDATA = WINDOWCOMPOSITIONATTRIBDATA FAR *; -using GetWindowCompositionAttributePtr = BOOL(WINAPI *)(HWND, PWINDOWCOMPOSITIONATTRIBDATA); -using SetWindowCompositionAttributePtr = BOOL(WINAPI *)(HWND, PWINDOWCOMPOSITIONATTRIBDATA); - using _WINDOWTHEMEATTRIBUTETYPE = enum _WINDOWTHEMEATTRIBUTETYPE { _WTA_NONCLIENT = 1 @@ -390,6 +387,37 @@ using WTA_OPTIONS2 = struct WTA_OPTIONS2 }; using PWTA_OPTIONS2 = WTA_OPTIONS2 *; +using IMMERSIVE_HC_CACHE_MODE = enum IMMERSIVE_HC_CACHE_MODE +{ + IHCM_USE_CACHED_VALUE = 0, + IHCM_REFRESH = 1 +}; + +using PREFERRED_APP_MODE = enum PREFERRED_APP_MODE +{ + PAM_DEFAULT = 0, + PAM_ALLOW_DARK = 1, + PAM_FORCE_DARK = 2, + PAM_FORCE_LIGHT = 3, + PAM_MAX = 4 +}; + +using GetWindowCompositionAttributePtr = BOOL(WINAPI *)(HWND, PWINDOWCOMPOSITIONATTRIBDATA); +using SetWindowCompositionAttributePtr = BOOL(WINAPI *)(HWND, PWINDOWCOMPOSITIONATTRIBDATA); +// Win10 1809 (10.0.17763) +using ShouldAppsUseDarkModePtr = BOOL(WINAPI *)(VOID); // Ordinal 132 +using AllowDarkModeForWindowPtr = BOOL(WINAPI *)(HWND, BOOL); // Ordinal 133 +using AllowDarkModeForAppPtr = BOOL(WINAPI *)(BOOL); // Ordinal 135 +using FlushMenuThemesPtr = VOID(WINAPI *)(VOID); // Ordinal 136 +using RefreshImmersiveColorPolicyStatePtr = VOID(WINAPI *)(VOID); // Ordinal 104 +using IsDarkModeAllowedForWindowPtr = BOOL(WINAPI *)(HWND); // Ordinal 137 +using GetIsImmersiveColorUsingHighContrastPtr = BOOL(WINAPI *)(IMMERSIVE_HC_CACHE_MODE); // Ordinal 106 +using OpenNcThemeDataPtr = HTHEME(WINAPI *)(HWND, LPCWSTR); // Ordinal 49 +// Win10 1903 (10.0.18362) +using ShouldSystemUseDarkModePtr = BOOL(WINAPI *)(VOID); // Ordinal 138 +using SetPreferredAppModePtr = PREFERRED_APP_MODE(WINAPI *)(PREFERRED_APP_MODE); // Ordinal 135 +using IsDarkModeAllowedForAppPtr = BOOL(WINAPI *)(VOID); // Ordinal 139 + EXTERN_C_START DECLSPEC_IMPORT MMRESULT WINAPI diff --git a/include/FramelessHelper/Core/framelesshelpercore_global.h b/include/FramelessHelper/Core/framelesshelpercore_global.h index 9928182..c64f77b 100644 --- a/include/FramelessHelper/Core/framelesshelpercore_global.h +++ b/include/FramelessHelper/Core/framelesshelpercore_global.h @@ -235,6 +235,11 @@ Q_NAMESPACE_EXPORT(FRAMELESSHELPER_CORE_API) [[maybe_unused]] inline Q_CONSTEXPR2 const QColor kDefaultSystemButtonBackgroundColor = {204, 204, 204}; // #CCCCCC [[maybe_unused]] inline Q_CONSTEXPR2 const QColor kDefaultSystemCloseButtonBackgroundColor = {232, 17, 35}; // #E81123 +[[maybe_unused]] inline const QByteArray kDontOverrideCursorVar + = FRAMELESSHELPER_BYTEARRAY_LITERAL("FRAMELESSHELPER_DONT_OVERRIDE_CURSOR"); +[[maybe_unused]] inline const QByteArray kDontToggleMaximizeVar + = FRAMELESSHELPER_BYTEARRAY_LITERAL("FRAMELESSHELPER_DONT_TOGGLE_MAXIMIZE"); + enum class Option { UseCrossPlatformQtImplementation = 0, @@ -373,10 +378,10 @@ Q_ENUM_NS(RegistryRootKey) enum class WindowEdge { Unspecified = 0x00000000, - Left = 0x00000002, - Top = 0x00000004, - Right = 0x00000008, - Bottom = 0x00000010 + Left = 0x00000001, + Top = 0x00000002, + Right = 0x00000004, + Bottom = 0x00000008 }; Q_ENUM_NS(WindowEdge) Q_DECLARE_FLAGS(WindowEdges, WindowEdge) diff --git a/include/FramelessHelper/Core/private/sysapiloader_p.h b/include/FramelessHelper/Core/private/sysapiloader_p.h index b093255..99f6892 100644 --- a/include/FramelessHelper/Core/private/sysapiloader_p.h +++ b/include/FramelessHelper/Core/private/sysapiloader_p.h @@ -41,6 +41,7 @@ public: Q_NODISCARD static SysApiLoader *instance(); + Q_NODISCARD static QFunctionPointer resolve(const QString &library, const char *function); Q_NODISCARD static QFunctionPointer resolve(const QString &library, const QByteArray &function); Q_NODISCARD static QFunctionPointer resolve(const QString &library, const QString &function); diff --git a/include/FramelessHelper/Core/utils.h b/include/FramelessHelper/Core/utils.h index 3f9600b..cccf61c 100644 --- a/include/FramelessHelper/Core/utils.h +++ b/include/FramelessHelper/Core/utils.h @@ -96,7 +96,6 @@ FRAMELESSHELPER_CORE_API void showSystemMenu( [[nodiscard]] FRAMELESSHELPER_CORE_API quint32 getTitleBarHeight(const WId windowId, const bool scaled); [[nodiscard]] FRAMELESSHELPER_CORE_API quint32 getFrameBorderThickness(const WId windowId, const bool scaled); -FRAMELESSHELPER_CORE_API void updateWindowFrameBorderColor(const WId windowId, const bool dark); FRAMELESSHELPER_CORE_API void maybeFixupQtInternals(const WId windowId); [[nodiscard]] FRAMELESSHELPER_CORE_API bool isWindowFrameBorderVisible(); [[nodiscard]] FRAMELESSHELPER_CORE_API bool isFrameBorderColorized(); @@ -115,6 +114,7 @@ FRAMELESSHELPER_CORE_API void forceSquareCornersForWindow(const WId windowId, co FRAMELESSHELPER_CORE_API void disableOriginalTitleBarFunctionalities (const WId windowId, const bool disable = true); FRAMELESSHELPER_CORE_API void setQtDarkModeAwareEnabled(const bool enable); +FRAMELESSHELPER_CORE_API void refreshWin32ThemeResources(const WId windowId, const bool dark); #endif // Q_OS_WINDOWS #ifdef Q_OS_LINUX diff --git a/src/core/cmakehelper.cmake b/src/core/cmakehelper.cmake index 62013f1..f8e0588 100644 --- a/src/core/cmakehelper.cmake +++ b/src/core/cmakehelper.cmake @@ -40,10 +40,10 @@ function(setup_compile_params arg_target) ) if(WIN32) # Needed by both MSVC and MinGW set(_WIN32_WINNT_WIN10 0x0A00) - set(NTDDI_WIN10_CO 0x0A00000B) + set(NTDDI_WIN10_NI 0x0A00000C) target_compile_definitions(${arg_target} PRIVATE WINVER=${_WIN32_WINNT_WIN10} _WIN32_WINNT=${_WIN32_WINNT_WIN10} - _WIN32_IE=${_WIN32_WINNT_WIN10} NTDDI_VERSION=${NTDDI_WIN10_CO} + _WIN32_IE=${_WIN32_WINNT_WIN10} NTDDI_VERSION=${NTDDI_WIN10_NI} ) endif() if(MSVC) diff --git a/src/core/framelesshelper_qt.cpp b/src/core/framelesshelper_qt.cpp index 40e57e8..a8106e1 100644 --- a/src/core/framelesshelper_qt.cpp +++ b/src/core/framelesshelper_qt.cpp @@ -41,9 +41,6 @@ Q_LOGGING_CATEGORY(lcFramelessHelperQt, "wangwenx190.framelesshelper.core.impl.q using namespace Global; -FRAMELESSHELPER_BYTEARRAY_CONSTANT2(DontOverrideCursorVar, "FRAMELESSHELPER_DONT_OVERRIDE_CURSOR") -FRAMELESSHELPER_BYTEARRAY_CONSTANT2(DontToggleMaximizeVar, "FRAMELESSHELPER_DONT_TOGGLE_MAXIMIZE") - struct QtHelperData { SystemParameters params = {}; diff --git a/src/core/framelesshelper_win.cpp b/src/core/framelesshelper_win.cpp index 689018f..0d64439 100644 --- a/src/core/framelesshelper_win.cpp +++ b/src/core/framelesshelper_win.cpp @@ -72,8 +72,6 @@ FRAMELESSHELPER_STRING_CONSTANT(SetWindowPos) FRAMELESSHELPER_STRING_CONSTANT(TrackMouseEvent) FRAMELESSHELPER_STRING_CONSTANT(FindWindowW) FRAMELESSHELPER_STRING_CONSTANT(UnregisterClassW) -FRAMELESSHELPER_BYTEARRAY_CONSTANT2(DontOverrideCursorVar, "FRAMELESSHELPER_DONT_OVERRIDE_CURSOR") -FRAMELESSHELPER_BYTEARRAY_CONSTANT2(DontToggleMaximizeVar, "FRAMELESSHELPER_DONT_TOGGLE_MAXIMIZE") FRAMELESSHELPER_STRING_CONSTANT(DestroyWindow) [[maybe_unused]] static constexpr const char kFallbackTitleBarErrorMessage[] = "FramelessHelper is unable to create the fallback title bar window, and thus the snap layout feature will be disabled" @@ -524,15 +522,15 @@ void FramelessHelperWin::addWindow(const SystemParameters ¶ms) if (WindowsVersionHelper::isWin10RS1OrGreater()) { // Tell DWM we may need dark theme non-client area (title bar & frame border). FramelessHelper::Core::setApplicationOSThemeAware(); - const bool dark = Utils::shouldAppsUseDarkMode(); - Utils::updateWindowFrameBorderColor(windowId, dark); if (WindowsVersionHelper::isWin10RS5OrGreater()) { + const bool dark = Utils::shouldAppsUseDarkMode(); static const bool isQtQuickApplication = (params.getCurrentApplicationType() == ApplicationType::Quick); if (isQtQuickApplication) { // Tell UXTheme we may need dark theme controls. // Causes some QtWidgets paint incorrectly, so only apply to Qt Quick applications. Utils::updateGlobalWin32ControlsTheme(windowId, dark); } + Utils::refreshWin32ThemeResources(windowId, dark); if (WindowsVersionHelper::isWin11OrGreater()) { const FramelessConfig * const config = FramelessConfig::instance(); // Set the frame corner style, only Win11 provides official public API to do it. @@ -1199,16 +1197,14 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me if ((wParam == 0) && (lParam != 0) // lParam sometimes may be NULL. && (std::wcscmp(reinterpret_cast(lParam), kThemeSettingChangeEventName) == 0)) { systemThemeChanged = true; - const bool dark = Utils::shouldAppsUseDarkMode(); -#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) - Utils::updateWindowFrameBorderColor(windowId, dark); -#endif if (WindowsVersionHelper::isWin10RS5OrGreater()) { + const bool dark = Utils::shouldAppsUseDarkMode(); static const bool isQtQuickApplication = (data.params.getCurrentApplicationType() == ApplicationType::Quick); if (isQtQuickApplication) { // Causes some QtWidgets paint incorrectly, so only apply to Qt Quick applications. Utils::updateGlobalWin32ControlsTheme(windowId, dark); } + Utils::refreshWin32ThemeResources(windowId, dark); } } } diff --git a/src/core/sysapiloader.cpp b/src/core/sysapiloader.cpp index 513fef8..8179a44 100644 --- a/src/core/sysapiloader.cpp +++ b/src/core/sysapiloader.cpp @@ -50,6 +50,20 @@ SysApiLoader *SysApiLoader::instance() return g_sysApiLoader(); } +QFunctionPointer SysApiLoader::resolve(const QString &library, const char *function) +{ + Q_ASSERT(!library.isEmpty()); + Q_ASSERT(function); + if (library.isEmpty() || !function) { + return nullptr; + } +#ifdef Q_OS_WINDOWS + return QSystemLibrary::resolve(library, function); +#else + return QLibrary::resolve(library, function.constData()); +#endif +} + QFunctionPointer SysApiLoader::resolve(const QString &library, const QByteArray &function) { Q_ASSERT(!library.isEmpty()); @@ -57,11 +71,7 @@ QFunctionPointer SysApiLoader::resolve(const QString &library, const QByteArray if (library.isEmpty() || function.isEmpty()) { return nullptr; } -#ifdef Q_OS_WINDOWS - return QSystemLibrary::resolve(library, function.constData()); -#else - return QLibrary::resolve(library, function.constData()); -#endif + return SysApiLoader::resolve(library, function.constData()); } QFunctionPointer SysApiLoader::resolve(const QString &library, const QString &function) diff --git a/src/core/utils.cpp b/src/core/utils.cpp index 90d2b89..c74af56 100644 --- a/src/core/utils.cpp +++ b/src/core/utils.cpp @@ -23,6 +23,9 @@ */ #include "utils.h" +#ifdef Q_OS_WINDOWS +# include "winverhelper_p.h" +#endif #include #include #include @@ -140,8 +143,7 @@ QString Utils::getSystemButtonIconCode(const SystemButtonType button) // Windows 11: Segoe Fluent Icons (https://docs.microsoft.com/en-us/windows/apps/design/style/segoe-fluent-icons-font) // Windows 10: Segoe MDL2 Assets (https://docs.microsoft.com/en-us/windows/apps/design/style/segoe-ui-symbol-font) // Windows 7~8.1: Micon (http://xtoolkit.github.io/Micon/) - static const bool isWin10OrGreater = isWindowsVersionOrGreater(WindowsVersion::_10_1507); - if (isWin10OrGreater) { + if (WindowsVersionHelper::isWin10OrGreater()) { return QChar(icon.segoe); } #endif diff --git a/src/core/utils_win.cpp b/src/core/utils_win.cpp index f75cfb3..8838b96 100644 --- a/src/core/utils_win.cpp +++ b/src/core/utils_win.cpp @@ -42,9 +42,48 @@ #include "sysapiloader_p.h" #include "registrykey_p.h" #include "winverhelper_p.h" -#include #include +EXTERN_C [[nodiscard]] FRAMELESSHELPER_CORE_API BOOL WINAPI +GetWindowCompositionAttribute(const HWND hWnd, PWINDOWCOMPOSITIONATTRIBDATA pvData) +{ + Q_ASSERT(hWnd); + Q_ASSERT(pvData); + if (!hWnd || !pvData) { + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + FRAMELESSHELPER_USE_NAMESPACE + FRAMELESSHELPER_STRING_CONSTANT(user32) + FRAMELESSHELPER_STRING_CONSTANT(GetWindowCompositionAttribute) + const auto loader = SysApiLoader::instance(); + if (!loader->isAvailable(kuser32, kGetWindowCompositionAttribute)) { + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return FALSE; + } + return (loader->get(kGetWindowCompositionAttribute))(hWnd, pvData); +} + +EXTERN_C [[nodiscard]] FRAMELESSHELPER_CORE_API BOOL WINAPI +SetWindowCompositionAttribute(const HWND hWnd, PWINDOWCOMPOSITIONATTRIBDATA pvData) +{ + Q_ASSERT(hWnd); + Q_ASSERT(pvData); + if (!hWnd || !pvData) { + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + FRAMELESSHELPER_USE_NAMESPACE + FRAMELESSHELPER_STRING_CONSTANT(user32) + FRAMELESSHELPER_STRING_CONSTANT(SetWindowCompositionAttribute) + const auto loader = SysApiLoader::instance(); + if (!loader->isAvailable(kuser32, kSetWindowCompositionAttribute)) { + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return FALSE; + } + return (loader->get(kSetWindowCompositionAttribute))(hWnd, pvData); +} + EXTERN_C [[nodiscard]] FRAMELESSHELPER_CORE_API HRESULT WINAPI SetWindowThemeAttribute2(const HWND hWnd, const _WINDOWTHEMEATTRIBUTETYPE attrib, PVOID pvData, const DWORD cbData @@ -55,13 +94,14 @@ SetWindowThemeAttribute2(const HWND hWnd, const _WINDOWTHEMEATTRIBUTETYPE attrib if (!hWnd || !pvData) { return E_INVALIDARG; } + FRAMELESSHELPER_USE_NAMESPACE FRAMELESSHELPER_STRING_CONSTANT(uxtheme) FRAMELESSHELPER_STRING_CONSTANT(SetWindowThemeAttribute) - const auto loader = FRAMELESSHELPER_PREPEND_NAMESPACE(SysApiLoader)::instance(); + const auto loader = SysApiLoader::instance(); if (!loader->isAvailable(kuxtheme, kSetWindowThemeAttribute)) { return E_NOTIMPL; } - return (loader->get(kSetWindowThemeAttribute))(hWnd, attrib, pvData, cbData); + return (loader->get(kSetWindowThemeAttribute))(hWnd, attrib, pvData, cbData); } EXTERN_C [[nodiscard]] FRAMELESSHELPER_CORE_API HRESULT WINAPI @@ -73,6 +113,187 @@ SetWindowThemeNonClientAttributes2(const HWND hWnd, const DWORD dwMask, const DW return SetWindowThemeAttribute2(hWnd, _WTA_NONCLIENT, &options, sizeof(options)); } +EXTERN_C [[nodiscard]] FRAMELESSHELPER_CORE_API BOOL WINAPI +ShouldAppsUseDarkMode(VOID) +{ + FRAMELESSHELPER_USE_NAMESPACE + FRAMELESSHELPER_STRING_CONSTANT(uxtheme) + static const auto pShouldAppsUseDarkMode + = reinterpret_cast( + SysApiLoader::resolve(kuxtheme, MAKEINTRESOURCEA(132))); + if (!pShouldAppsUseDarkMode) { + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return FALSE; + } + return pShouldAppsUseDarkMode(); +} + +EXTERN_C [[nodiscard]] FRAMELESSHELPER_CORE_API BOOL WINAPI +AllowDarkModeForWindow(const HWND hWnd, const BOOL bAllow) +{ + Q_ASSERT(hWnd); + if (!hWnd) { + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + FRAMELESSHELPER_USE_NAMESPACE + FRAMELESSHELPER_STRING_CONSTANT(uxtheme) + static const auto pAllowDarkModeForWindow + = reinterpret_cast( + SysApiLoader::resolve(kuxtheme, MAKEINTRESOURCEA(133))); + if (!pAllowDarkModeForWindow) { + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return FALSE; + } + return pAllowDarkModeForWindow(hWnd, bAllow); +} + +EXTERN_C [[nodiscard]] FRAMELESSHELPER_CORE_API BOOL WINAPI +AllowDarkModeForApp(const BOOL bAllow) +{ + FRAMELESSHELPER_USE_NAMESPACE + FRAMELESSHELPER_STRING_CONSTANT(uxtheme) + static const auto pAllowDarkModeForApp + = reinterpret_cast( + SysApiLoader::resolve(kuxtheme, MAKEINTRESOURCEA(135))); + if (!pAllowDarkModeForApp) { + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return FALSE; + } + return pAllowDarkModeForApp(bAllow); +} + +EXTERN_C FRAMELESSHELPER_CORE_API VOID WINAPI +FlushMenuThemes(VOID) +{ + FRAMELESSHELPER_USE_NAMESPACE + FRAMELESSHELPER_STRING_CONSTANT(uxtheme) + static const auto pFlushMenuThemes + = reinterpret_cast( + SysApiLoader::resolve(kuxtheme, MAKEINTRESOURCEA(136))); + if (!pFlushMenuThemes) { + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return; + } + pFlushMenuThemes(); +} + +EXTERN_C FRAMELESSHELPER_CORE_API VOID WINAPI +RefreshImmersiveColorPolicyState(VOID) +{ + FRAMELESSHELPER_USE_NAMESPACE + FRAMELESSHELPER_STRING_CONSTANT(uxtheme) + static const auto pRefreshImmersiveColorPolicyState + = reinterpret_cast( + SysApiLoader::resolve(kuxtheme, MAKEINTRESOURCEA(104))); + if (!pRefreshImmersiveColorPolicyState) { + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return; + } + pRefreshImmersiveColorPolicyState(); +} + +EXTERN_C [[nodiscard]] FRAMELESSHELPER_CORE_API BOOL WINAPI +IsDarkModeAllowedForWindow(const HWND hWnd) +{ + Q_ASSERT(hWnd); + if (!hWnd) { + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + FRAMELESSHELPER_USE_NAMESPACE + FRAMELESSHELPER_STRING_CONSTANT(uxtheme) + static const auto pIsDarkModeAllowedForWindow + = reinterpret_cast( + SysApiLoader::resolve(kuxtheme, MAKEINTRESOURCEA(137))); + if (!pIsDarkModeAllowedForWindow) { + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return FALSE; + } + return pIsDarkModeAllowedForWindow(hWnd); +} + +EXTERN_C [[nodiscard]] FRAMELESSHELPER_CORE_API BOOL WINAPI +GetIsImmersiveColorUsingHighContrast(const IMMERSIVE_HC_CACHE_MODE mode) +{ + FRAMELESSHELPER_USE_NAMESPACE + FRAMELESSHELPER_STRING_CONSTANT(uxtheme) + static const auto pGetIsImmersiveColorUsingHighContrast + = reinterpret_cast( + SysApiLoader::resolve(kuxtheme, MAKEINTRESOURCEA(106))); + if (!pGetIsImmersiveColorUsingHighContrast) { + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return FALSE; + } + return pGetIsImmersiveColorUsingHighContrast(mode); +} + +EXTERN_C [[nodiscard]] FRAMELESSHELPER_CORE_API HTHEME WINAPI +OpenNcThemeData(const HWND hWnd, LPCWSTR pszClassList) +{ + Q_ASSERT(hWnd); + Q_ASSERT(pszClassList); + if (!hWnd || !pszClassList) { + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + FRAMELESSHELPER_USE_NAMESPACE + FRAMELESSHELPER_STRING_CONSTANT(uxtheme) + static const auto pOpenNcThemeData + = reinterpret_cast( + SysApiLoader::resolve(kuxtheme, MAKEINTRESOURCEA(49))); + if (!pOpenNcThemeData) { + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return FALSE; + } + return pOpenNcThemeData(hWnd, pszClassList); +} + +EXTERN_C [[nodiscard]] FRAMELESSHELPER_CORE_API BOOL WINAPI +ShouldSystemUseDarkMode(VOID) +{ + FRAMELESSHELPER_USE_NAMESPACE + FRAMELESSHELPER_STRING_CONSTANT(uxtheme) + static const auto pShouldSystemUseDarkMode + = reinterpret_cast( + SysApiLoader::resolve(kuxtheme, MAKEINTRESOURCEA(138))); + if (!pShouldSystemUseDarkMode) { + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return FALSE; + } + return pShouldSystemUseDarkMode(); +} + +EXTERN_C [[nodiscard]] FRAMELESSHELPER_CORE_API PREFERRED_APP_MODE WINAPI +SetPreferredAppMode(const PREFERRED_APP_MODE mode) +{ + FRAMELESSHELPER_USE_NAMESPACE + FRAMELESSHELPER_STRING_CONSTANT(uxtheme) + static const auto pSetPreferredAppMode + = reinterpret_cast( + SysApiLoader::resolve(kuxtheme, MAKEINTRESOURCEA(135))); + if (!pSetPreferredAppMode) { + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return PAM_MAX; + } + return pSetPreferredAppMode(mode); +} + +EXTERN_C [[nodiscard]] FRAMELESSHELPER_CORE_API BOOL WINAPI +IsDarkModeAllowedForApp(VOID) +{ + FRAMELESSHELPER_USE_NAMESPACE + FRAMELESSHELPER_STRING_CONSTANT(uxtheme) + static const auto pIsDarkModeAllowedForApp + = reinterpret_cast( + SysApiLoader::resolve(kuxtheme, MAKEINTRESOURCEA(139))); + if (!pIsDarkModeAllowedForApp) { + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return FALSE; + } + return pIsDarkModeAllowedForApp(); +} + Q_DECLARE_METATYPE(QMargins) FRAMELESSHELPER_BEGIN_NAMESPACE @@ -169,6 +390,11 @@ FRAMELESSHELPER_STRING_CONSTANT(DeleteDC) FRAMELESSHELPER_STRING_CONSTANT(d2d1) FRAMELESSHELPER_STRING_CONSTANT(D2D1CreateFactory) FRAMELESSHELPER_STRING_CONSTANT(ReloadSystemMetrics) +FRAMELESSHELPER_STRING_CONSTANT(SetPreferredAppMode) +FRAMELESSHELPER_STRING_CONSTANT(AllowDarkModeForApp) +FRAMELESSHELPER_STRING_CONSTANT(AllowDarkModeForWindow) +FRAMELESSHELPER_STRING_CONSTANT(FlushMenuThemes) +FRAMELESSHELPER_STRING_CONSTANT(RefreshImmersiveColorPolicyState) struct Win32UtilsHelperData { @@ -474,8 +700,7 @@ bool Utils::isWindowsVersionOrGreater(const WindowsVersion version) bool Utils::isDwmCompositionEnabled() { // DWM composition is always enabled and can't be disabled since Windows 8. - static const bool isWin8OrGreater = isWindowsVersionOrGreater(WindowsVersion::_8); - if (isWin8OrGreater) { + if (WindowsVersionHelper::isWin8OrGreater()) { return true; } const auto resultFromRegistry = []() -> bool { @@ -627,8 +852,7 @@ QColor Utils::getDwmColorizationColor() DwmColorizationArea Utils::getDwmColorizationArea() { // It's a Win10 only feature. (TO BE VERIFIED) - static const bool isWin10OrGreater = isWindowsVersionOrGreater(WindowsVersion::_10_1507); - if (!isWin10OrGreater) { + if (!WindowsVersionHelper::isWin10OrGreater()) { return DwmColorizationArea::None_; } const RegistryKey themeRegistry(RegistryRootKey::CurrentUser, personalizeRegistryKey()); @@ -1035,8 +1259,7 @@ quint32 Utils::getFrameBorderThickness(const WId windowId, const bool scaled) return 0; } // There's no window frame border before Windows 10. - static const bool isWin10OrGreater = isWindowsVersionOrGreater(WindowsVersion::_10_1507); - if (!isWin10OrGreater) { + if (!WindowsVersionHelper::isWin10OrGreater()) { return 0; } if (!API_DWM_AVAILABLE(DwmGetWindowAttribute)) { @@ -1061,8 +1284,7 @@ QColor Utils::getFrameBorderColor(const bool active) { // There's no window frame border before Windows 10. // So we just return a default value which is based on most window managers. - static const bool isWin10OrGreater = isWindowsVersionOrGreater(WindowsVersion::_10_1507); - if (!isWin10OrGreater) { + if (!WindowsVersionHelper::isWin10OrGreater()) { return (active ? kDefaultBlackColor : kDefaultDarkGrayColor); } const bool dark = shouldAppsUseDarkMode(); @@ -1077,30 +1299,6 @@ QColor Utils::getFrameBorderColor(const bool active) } } -void Utils::updateWindowFrameBorderColor(const WId windowId, const bool dark) -{ - Q_ASSERT(windowId); - if (!windowId) { - return; - } - // There's no global dark theme before Win10 1607. - static const bool isWin10RS1OrGreater = isWindowsVersionOrGreater(WindowsVersion::_10_1607); - if (!isWin10RS1OrGreater) { - return; - } - if (!API_DWM_AVAILABLE(DwmSetWindowAttribute)) { - return; - } - const auto hwnd = reinterpret_cast(windowId); - static const bool isWin1020H1OrGreater = isWindowsVersionOrGreater(WindowsVersion::_10_2004); - const DWORD mode = (isWin1020H1OrGreater ? _DWMWA_USE_IMMERSIVE_DARK_MODE : _DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1); - const BOOL value = (dark ? TRUE : FALSE); - const HRESULT hr = API_CALL_FUNCTION(DwmSetWindowAttribute, hwnd, mode, &value, sizeof(value)); - if (FAILED(hr)) { - WARNING << __getSystemErrorMessage(kDwmSetWindowAttribute, hr); - } -} - void Utils::maybeFixupQtInternals(const WId windowId) { Q_ASSERT(windowId); @@ -1216,8 +1414,7 @@ bool Utils::isWindowFrameBorderVisible() if (config->isSet(Option::ForceHideWindowFrameBorder)) { return false; } - static const bool isWin10OrGreater = isWindowsVersionOrGreater(WindowsVersion::_10_1507); - return isWin10OrGreater; + return WindowsVersionHelper::isWin10OrGreater(); }(); return result; } @@ -1225,8 +1422,7 @@ bool Utils::isWindowFrameBorderVisible() bool Utils::isTitleBarColorized() { // CHECK: is it supported on win7? - static const bool isWin10OrGreater = isWindowsVersionOrGreater(WindowsVersion::_10_1507); - if (!isWin10OrGreater) { + if (!WindowsVersionHelper::isWin10OrGreater()) { return false; } const DwmColorizationArea area = getDwmColorizationArea(); @@ -1407,8 +1603,7 @@ SystemTheme Utils::getSystemTheme() if (isHighContrastModeEnabled()) { return SystemTheme::HighContrast; } - static const bool isWin10RS1OrGreater = isWindowsVersionOrGreater(WindowsVersion::_10_1607); - if (isWin10RS1OrGreater && shouldAppsUseDarkMode()) { + if (WindowsVersionHelper::isWin10RS1OrGreater() && shouldAppsUseDarkMode()) { return SystemTheme::Dark; } return SystemTheme::Light; @@ -1421,8 +1616,7 @@ void Utils::updateGlobalWin32ControlsTheme(const WId windowId, const bool dark) return; } // There's no global dark theme for common Win32 controls before Win10 1809. - static const bool isWin10RS5OrGreater = isWindowsVersionOrGreater(WindowsVersion::_10_1809); - if (!isWin10RS5OrGreater) { + if (!WindowsVersionHelper::isWin10RS5OrGreater()) { return; } if (!API_THEME_AVAILABLE(SetWindowTheme)) { @@ -1439,8 +1633,7 @@ void Utils::updateGlobalWin32ControlsTheme(const WId windowId, const bool dark) bool Utils::shouldAppsUseDarkMode_windows() { // The global dark mode was first introduced in Windows 10 1607. - static const bool isWin10RS1OrGreater = isWindowsVersionOrGreater(WindowsVersion::_10_1607); - if (!isWin10RS1OrGreater) { + if (!WindowsVersionHelper::isWin10RS1OrGreater()) { return false; } #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) @@ -1486,8 +1679,7 @@ void Utils::forceSquareCornersForWindow(const WId windowId, const bool force) return; } // We cannot change the window corner style until Windows 11. - static const bool isWin11OrGreater = isWindowsVersionOrGreater(WindowsVersion::_11_21H2); - if (!isWin11OrGreater) { + if (!WindowsVersionHelper::isWin11OrGreater()) { return; } if (!API_DWM_AVAILABLE(DwmSetWindowAttribute)) { @@ -1508,44 +1700,34 @@ bool Utils::setBlurBehindWindowEnabled(const WId windowId, const BlurMode mode, return false; } const auto hwnd = reinterpret_cast(windowId); - static const bool isWin8OrGreater = isWindowsVersionOrGreater(WindowsVersion::_8); - if (isWin8OrGreater) { - if (!API_USER_AVAILABLE(SetWindowCompositionAttribute)) { - return false; - } + if (WindowsVersionHelper::isWin8OrGreater()) { if (!API_DWM_AVAILABLE(DwmSetWindowAttribute)) { return false; } if (!API_DWM_AVAILABLE(DwmExtendFrameIntoClientArea)) { return false; } - const auto pSetWindowCompositionAttribute = - reinterpret_cast( - SysApiLoader::instance()->get(kSetWindowCompositionAttribute)); - static const bool isWin1122H2OrGreater = isWindowsVersionOrGreater(WindowsVersion::_11_22H2); - static const bool isWin11OrGreater = isWindowsVersionOrGreater(WindowsVersion::_11_21H2); - static const bool isWin10OrGreater = isWindowsVersionOrGreater(WindowsVersion::_10_1507); const BlurMode blurMode = [mode]() -> BlurMode { if ((mode == BlurMode::Disable) || (mode == BlurMode::Windows_Aero)) { return mode; } - if ((mode == BlurMode::Windows_Mica) && !isWin11OrGreater) { + if ((mode == BlurMode::Windows_Mica) && !WindowsVersionHelper::isWin11OrGreater()) { WARNING << "The Mica material is not supported on your system, fallback to the Acrylic blur instead..."; - if (isWin10OrGreater) { + if (WindowsVersionHelper::isWin10OrGreater()) { return BlurMode::Windows_Acrylic; } WARNING << "The Acrylic blur is not supported on your system, fallback to the traditional DWM blur instead..."; return BlurMode::Windows_Aero; } - if ((mode == BlurMode::Windows_Acrylic) && !isWin10OrGreater) { + if ((mode == BlurMode::Windows_Acrylic) && !WindowsVersionHelper::isWin10OrGreater()) { WARNING << "The Acrylic blur is not supported on your system, fallback to the traditional DWM blur instead..."; return BlurMode::Windows_Aero; } if (mode == BlurMode::Default) { - if (isWin11OrGreater) { + if (WindowsVersionHelper::isWin11OrGreater()) { return BlurMode::Windows_Mica; } - if (isWin10OrGreater) { + if (WindowsVersionHelper::isWin10OrGreater()) { return BlurMode::Windows_Acrylic; } return BlurMode::Windows_Aero; @@ -1554,7 +1736,7 @@ bool Utils::setBlurBehindWindowEnabled(const WId windowId, const BlurMode mode, return mode; }(); if (blurMode == BlurMode::Disable) { - if (isWin1122H2OrGreater) { + if (WindowsVersionHelper::isWin1122H2OrGreater()) { const _DWM_SYSTEMBACKDROP_TYPE dwmsbt = _DWMSBT_NONE; const HRESULT hr = API_CALL_FUNCTION(DwmSetWindowAttribute, hwnd, _DWMWA_SYSTEMBACKDROP_TYPE, &dwmsbt, sizeof(dwmsbt)); @@ -1562,7 +1744,7 @@ bool Utils::setBlurBehindWindowEnabled(const WId windowId, const BlurMode mode, WARNING << __getSystemErrorMessage(kDwmSetWindowAttribute, hr); } } - if (isWin11OrGreater) { + if (WindowsVersionHelper::isWin11OrGreater()) { const BOOL enable = FALSE; HRESULT hr = API_CALL_FUNCTION(DwmSetWindowAttribute, hwnd, _DWMWA_MICA_EFFECT, &enable, sizeof(enable)); @@ -1583,7 +1765,7 @@ bool Utils::setBlurBehindWindowEnabled(const WId windowId, const BlurMode mode, wcad.Attrib = WCA_ACCENT_POLICY; wcad.pvData = &policy; wcad.cbData = sizeof(policy); - if (pSetWindowCompositionAttribute(hwnd, &wcad) == FALSE) { + if (SetWindowCompositionAttribute(hwnd, &wcad) == FALSE) { WARNING << getSystemErrorMessage(kSetWindowCompositionAttribute); } } @@ -1597,7 +1779,7 @@ bool Utils::setBlurBehindWindowEnabled(const WId windowId, const BlurMode mode, const MARGINS margins = {-1, -1, -1, -1}; HRESULT hr = API_CALL_FUNCTION(DwmExtendFrameIntoClientArea, hwnd, &margins); if (SUCCEEDED(hr)) { - if (isWin1122H2OrGreater) { + if (WindowsVersionHelper::isWin1122H2OrGreater()) { const _DWM_SYSTEMBACKDROP_TYPE dwmsbt = _DWMSBT_MAINWINDOW; // Mica hr = API_CALL_FUNCTION(DwmSetWindowAttribute, hwnd, _DWMWA_SYSTEMBACKDROP_TYPE, &dwmsbt, sizeof(dwmsbt)); @@ -1646,8 +1828,8 @@ bool Utils::setBlurBehindWindowEnabled(const WId windowId, const BlurMode mode, wcad.Attrib = WCA_ACCENT_POLICY; wcad.pvData = &policy; wcad.cbData = sizeof(policy); - if (pSetWindowCompositionAttribute(hwnd, &wcad) != FALSE) { - if (!isWin11OrGreater) { + if (SetWindowCompositionAttribute(hwnd, &wcad) != FALSE) { + if (!WindowsVersionHelper::isWin11OrGreater()) { DEBUG << "Enabling the Acrylic blur for Win32 windows on Windows 10 " "is very buggy. The only recommended way by Microsoft is to " "use the XAML Island technology or use pure UWP instead. If " @@ -1749,8 +1931,7 @@ bool Utils::isBlurBehindWindowSupported() if (FramelessConfig::instance()->isSet(Option::ForceNonNativeBackgroundBlur)) { return false; } - static const bool isWin11OrGreater = isWindowsVersionOrGreater(WindowsVersion::_11_21H2); - return isWin11OrGreater; + return WindowsVersionHelper::isWin11OrGreater(); }(); return result; } @@ -1817,4 +1998,98 @@ void Utils::registerThemeChangeNotification() // top level windows by default. } +void Utils::refreshWin32ThemeResources(const WId windowId, const bool dark) +{ + Q_ASSERT(windowId); + if (!windowId) { + return; + } + // We have no way to adjust such things until Win10 1809. + if (!WindowsVersionHelper::isWin10RS5OrGreater()) { + return; + } +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + if (!API_DWM_AVAILABLE(DwmSetWindowAttribute)) { + return; + } +#endif + const auto hWnd = reinterpret_cast(windowId); +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + const DWORD borderFlag = (WindowsVersionHelper::isWin1020H1OrGreater() + ? _DWMWA_USE_IMMERSIVE_DARK_MODE : _DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1); +#endif + const PREFERRED_APP_MODE appMode = (dark ? PAM_ALLOW_DARK : PAM_DEFAULT); + const BOOL darkFlag = (dark ? TRUE : FALSE); + WINDOWCOMPOSITIONATTRIBDATA wcad; + SecureZeroMemory(&wcad, sizeof(wcad)); + wcad.Attrib = WCA_USEDARKMODECOLORS; + wcad.pvData = const_cast(&darkFlag); + wcad.cbData = sizeof(darkFlag); + if (dark) { + if (WindowsVersionHelper::isWin1019H1OrGreater()) { + if (SetPreferredAppMode(appMode) == PAM_MAX) { + WARNING << getSystemErrorMessage(kSetPreferredAppMode); + } + } else { + if (AllowDarkModeForApp(darkFlag) == FALSE) { + WARNING << getSystemErrorMessage(kAllowDarkModeForApp); + } + } + if (AllowDarkModeForWindow(hWnd, darkFlag) == FALSE) { + WARNING << getSystemErrorMessage(kAllowDarkModeForWindow); + } + if (SetWindowCompositionAttribute(hWnd, &wcad) == FALSE) { + WARNING << getSystemErrorMessage(kSetWindowCompositionAttribute); + } +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + const HRESULT hr = API_CALL_FUNCTION(DwmSetWindowAttribute, hWnd, borderFlag, &darkFlag, sizeof(darkFlag)); + if (FAILED(hr)) { + WARNING << __getSystemErrorMessage(kDwmSetWindowAttribute, hr); + } +#endif + SetLastError(ERROR_SUCCESS); + FlushMenuThemes(); + if (GetLastError() != ERROR_SUCCESS) { + WARNING << getSystemErrorMessage(kFlushMenuThemes); + } + SetLastError(ERROR_SUCCESS); + RefreshImmersiveColorPolicyState(); + if (GetLastError() != ERROR_SUCCESS) { + WARNING << getSystemErrorMessage(kRefreshImmersiveColorPolicyState); + } + } else { + if (AllowDarkModeForWindow(hWnd, darkFlag) == FALSE) { + WARNING << getSystemErrorMessage(kAllowDarkModeForWindow); + } + if (SetWindowCompositionAttribute(hWnd, &wcad) == FALSE) { + WARNING << getSystemErrorMessage(kSetWindowCompositionAttribute); + } +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + const HRESULT hr = API_CALL_FUNCTION(DwmSetWindowAttribute, hWnd, borderFlag, &darkFlag, sizeof(darkFlag)); + if (FAILED(hr)) { + WARNING << __getSystemErrorMessage(kDwmSetWindowAttribute, hr); + } +#endif + SetLastError(ERROR_SUCCESS); + FlushMenuThemes(); + if (GetLastError() != ERROR_SUCCESS) { + WARNING << getSystemErrorMessage(kFlushMenuThemes); + } + SetLastError(ERROR_SUCCESS); + RefreshImmersiveColorPolicyState(); + if (GetLastError() != ERROR_SUCCESS) { + WARNING << getSystemErrorMessage(kRefreshImmersiveColorPolicyState); + } + if (WindowsVersionHelper::isWin1019H1OrGreater()) { + if (SetPreferredAppMode(appMode) == PAM_MAX) { + WARNING << getSystemErrorMessage(kSetPreferredAppMode); + } + } else { + if (AllowDarkModeForApp(darkFlag) == FALSE) { + WARNING << getSystemErrorMessage(kAllowDarkModeForApp); + } + } + } +} + FRAMELESSHELPER_END_NAMESPACE diff --git a/tools/dpitester/CMakeLists.txt b/tools/dpitester/CMakeLists.txt index 4d00dc4..9d2726a 100644 --- a/tools/dpitester/CMakeLists.txt +++ b/tools/dpitester/CMakeLists.txt @@ -29,14 +29,14 @@ target_sources(${PROJECT_NAME} PRIVATE ) set(_WIN32_WINNT_WIN10 0x0A00) -set(NTDDI_WIN10_CO 0x0A00000B) +set(NTDDI_WIN10_NI 0x0A00000C) target_compile_definitions(${PROJECT_NAME} PRIVATE _CRT_NON_CONFORMING_SWPRINTFS _CRT_SECURE_NO_WARNINGS _CRT_SECURE_NO_DEPRECATE _CRT_NONSTDC_NO_WARNINGS _CRT_NONSTDC_NO_DEPRECATE _ENABLE_EXTENDED_ALIGNED_STORAGE NOMINMAX UNICODE _UNICODE WIN32_LEAN_AND_MEAN WINRT_LEAN_AND_MEAN WINVER=${_WIN32_WINNT_WIN10} _WIN32_WINNT=${_WIN32_WINNT_WIN10} - _WIN32_IE=${_WIN32_WINNT_WIN10} NTDDI_VERSION=${NTDDI_WIN10_CO} + _WIN32_IE=${_WIN32_WINNT_WIN10} NTDDI_VERSION=${NTDDI_WIN10_NI} ) target_compile_options(${PROJECT_NAME} PRIVATE