diff --git a/CMakeLists.txt b/CMakeLists.txt index 8a25916..d346455 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,8 +17,8 @@ set(CMAKE_AUTOUIC ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) -find_package(QT NAMES Qt6 Qt5 COMPONENTS Gui REQUIRED) -find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Gui REQUIRED) +find_package(QT NAMES Qt6 Qt5 COMPONENTS Core Gui REQUIRED) +find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Gui REQUIRED) find_package(QT NAMES Qt6 Qt5 COMPONENTS Quick) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Quick) @@ -83,13 +83,8 @@ if(TEST_UNIX) ) endif() -if(WIN32) - target_link_libraries(${PROJECT_NAME} PRIVATE - dwmapi winmm - ) -endif() - target_link_libraries(${PROJECT_NAME} PRIVATE + Qt${QT_VERSION_MAJOR}::CorePrivate Qt${QT_VERSION_MAJOR}::GuiPrivate ) diff --git a/examples/common.pri b/examples/common.pri index c3ec76e..9cb63f4 100644 --- a/examples/common.pri +++ b/examples/common.pri @@ -10,7 +10,7 @@ RESOURCES += $$PWD/images.qrc win32 { CONFIG += windeployqt CONFIG -= embed_manifest_exe - LIBS += -luser32 -lshell32 -ldwmapi -lwinmm + LIBS += -luser32 -lshell32 RC_FILE = $$PWD/example.rc OTHER_FILES += $$PWD/example.manifest } diff --git a/examples/mainwindow/CMakeLists.txt b/examples/mainwindow/CMakeLists.txt index f7870c6..627e6d9 100644 --- a/examples/mainwindow/CMakeLists.txt +++ b/examples/mainwindow/CMakeLists.txt @@ -33,9 +33,5 @@ target_compile_definitions(MainWindow PRIVATE QT_NO_CAST_TO_ASCII QT_NO_KEYWORDS QT_DEPRECATED_WARNINGS - QT_DISABLE_DEPRECATED_BEFORE=0x060100 + QT_DISABLE_DEPRECATED_BEFORE=0x060200 ) - -if(WIN32) - target_link_libraries(MainWindow PRIVATE dwmapi) -endif() diff --git a/examples/quick/CMakeLists.txt b/examples/quick/CMakeLists.txt index 4e85d0a..bcd1ac1 100644 --- a/examples/quick/CMakeLists.txt +++ b/examples/quick/CMakeLists.txt @@ -31,9 +31,5 @@ target_compile_definitions(Quick PRIVATE QT_NO_CAST_TO_ASCII QT_NO_KEYWORDS QT_DEPRECATED_WARNINGS - QT_DISABLE_DEPRECATED_BEFORE=0x060100 + QT_DISABLE_DEPRECATED_BEFORE=0x060200 ) - -if(WIN32) - target_link_libraries(Quick PRIVATE dwmapi) -endif() diff --git a/examples/quick/quick.pro b/examples/quick/quick.pro index 5c98df4..fc62840 100644 --- a/examples/quick/quick.pro +++ b/examples/quick/quick.pro @@ -1,7 +1,6 @@ TARGET = Quick TEMPLATE = app QT += quick quickcontrols2 -CONFIG(release, debug|release): CONFIG += qtquickcompiler SOURCES += main.cpp RESOURCES += qml.qrc include($$PWD/../common.pri) diff --git a/examples/widget/CMakeLists.txt b/examples/widget/CMakeLists.txt index 83e847b..d64c8e5 100644 --- a/examples/widget/CMakeLists.txt +++ b/examples/widget/CMakeLists.txt @@ -31,9 +31,5 @@ target_compile_definitions(Widget PRIVATE QT_NO_CAST_TO_ASCII QT_NO_KEYWORDS QT_DEPRECATED_WARNINGS - QT_DISABLE_DEPRECATED_BEFORE=0x060100 + QT_DISABLE_DEPRECATED_BEFORE=0x060200 ) - -if(WIN32) - target_link_libraries(Widget PRIVATE dwmapi) -endif() diff --git a/framelesshelper_win32.cpp b/framelesshelper_win32.cpp index 26bb270..f55a4d8 100644 --- a/framelesshelper_win32.cpp +++ b/framelesshelper_win32.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include "utilities.h" #include "framelesshelper_windows.h" @@ -140,7 +141,7 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me } #if (QT_VERSION == QT_VERSION_CHECK(5, 11, 1)) // Work-around a bug caused by typo which only exists in Qt 5.11.1 - const auto msg = *reinterpret_cast(message); + const auto msg = *static_cast(message); #else const auto msg = static_cast(message); #endif @@ -377,62 +378,73 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me clientRect->bottom += 1; #endif if (Utilities::isDwmCompositionAvailable()) { - // Dirty hack to workaround the resize flicker caused by DWM. - LARGE_INTEGER freq = {}; - if (QueryPerformanceFrequency(&freq) == FALSE) { - qWarning() << Utilities::getSystemErrorMessage(QStringLiteral("QueryPerformanceFrequency")); - break; - } - TIMECAPS tc = {}; - if (timeGetDevCaps(&tc, sizeof(tc)) != MMSYSERR_NOERROR) { - qWarning() << "timeGetDevCaps() failed."; - break; - } - const UINT ms_granularity = tc.wPeriodMin; - if (timeBeginPeriod(ms_granularity) != TIMERR_NOERROR) { - qWarning() << "timeBeginPeriod() failed."; - break; - } - LARGE_INTEGER now0 = {}; - if (QueryPerformanceCounter(&now0) == FALSE) { - qWarning() << Utilities::getSystemErrorMessage(QStringLiteral("QueryPerformanceCounter")); - break; - } - // ask DWM where the vertical blank falls - DWM_TIMING_INFO dti; - SecureZeroMemory(&dti, sizeof(dti)); - dti.cbSize = sizeof(dti); - const HRESULT hr = DwmGetCompositionTimingInfo(nullptr, &dti); - if (FAILED(hr)) { - qWarning() << Utilities::getSystemErrorMessage(QStringLiteral("DwmGetCompositionTimingInfo")); - break; - } - LARGE_INTEGER now1 = {}; - if (QueryPerformanceCounter(&now1) == FALSE) { - qWarning() << Utilities::getSystemErrorMessage(QStringLiteral("QueryPerformanceCounter")); - break; - } - // - DWM told us about SOME vertical blank - // - past or future, possibly many frames away - // - convert that into the NEXT vertical blank - const LONGLONG period = dti.qpcRefreshPeriod; - const LONGLONG dt = dti.qpcVBlank - now1.QuadPart; - LONGLONG w = 0, m = 0; - if (dt >= 0) { - w = dt / period; - } else { - // reach back to previous period - // - so m represents consistent position within phase - w = -1 + dt / period; - } - m = dt - (period * w); - Q_ASSERT(m >= 0); - Q_ASSERT(m < period); - const qreal m_ms = 1000.0 * static_cast(m) / static_cast(freq.QuadPart); - Sleep(static_cast(qRound(m_ms))); - if (timeEndPeriod(ms_granularity) != TIMERR_NOERROR) { - qWarning() << "timeEndPeriod() failed."; - break; + const QString winmm = QStringLiteral("winmm"); + static const auto ptimeGetDevCaps = + reinterpret_cast(QSystemLibrary::resolve(winmm, "timeGetDevCaps")); + static const auto ptimeBeginPeriod = + reinterpret_cast(QSystemLibrary::resolve(winmm, "timeBeginPeriod")); + static const auto ptimeEndPeriod = + reinterpret_cast(QSystemLibrary::resolve(winmm, "timeEndPeriod")); + static const auto pDwmGetCompositionTimingInfo = + reinterpret_cast(QSystemLibrary::resolve(QStringLiteral("dwmapi"), "DwmGetCompositionTimingInfo")); + if (ptimeGetDevCaps && ptimeBeginPeriod && ptimeEndPeriod && pDwmGetCompositionTimingInfo) { + // Dirty hack to workaround the resize flicker caused by DWM. + LARGE_INTEGER freq = {}; + if (QueryPerformanceFrequency(&freq) == FALSE) { + qWarning() << Utilities::getSystemErrorMessage(QStringLiteral("QueryPerformanceFrequency")); + break; + } + TIMECAPS tc = {}; + if (ptimeGetDevCaps(&tc, sizeof(tc)) != MMSYSERR_NOERROR) { + qWarning() << "timeGetDevCaps() failed."; + break; + } + const UINT ms_granularity = tc.wPeriodMin; + if (ptimeBeginPeriod(ms_granularity) != TIMERR_NOERROR) { + qWarning() << "timeBeginPeriod() failed."; + break; + } + LARGE_INTEGER now0 = {}; + if (QueryPerformanceCounter(&now0) == FALSE) { + qWarning() << Utilities::getSystemErrorMessage(QStringLiteral("QueryPerformanceCounter")); + break; + } + // ask DWM where the vertical blank falls + DWM_TIMING_INFO dti; + SecureZeroMemory(&dti, sizeof(dti)); + dti.cbSize = sizeof(dti); + const HRESULT hr = pDwmGetCompositionTimingInfo(nullptr, &dti); + if (FAILED(hr)) { + qWarning() << Utilities::getSystemErrorMessage(QStringLiteral("DwmGetCompositionTimingInfo")); + break; + } + LARGE_INTEGER now1 = {}; + if (QueryPerformanceCounter(&now1) == FALSE) { + qWarning() << Utilities::getSystemErrorMessage(QStringLiteral("QueryPerformanceCounter")); + break; + } + // - DWM told us about SOME vertical blank + // - past or future, possibly many frames away + // - convert that into the NEXT vertical blank + const LONGLONG period = dti.qpcRefreshPeriod; + const LONGLONG dt = dti.qpcVBlank - now1.QuadPart; + LONGLONG w = 0, m = 0; + if (dt >= 0) { + w = dt / period; + } else { + // reach back to previous period + // - so m represents consistent position within phase + w = -1 + dt / period; + } + m = dt - (period * w); + Q_ASSERT(m >= 0); + Q_ASSERT(m < period); + const qreal m_ms = 1000.0 * static_cast(m) / static_cast(freq.QuadPart); + Sleep(static_cast(qRound(m_ms))); + if (ptimeEndPeriod(ms_granularity) != TIMERR_NOERROR) { + qWarning() << "timeEndPeriod() failed."; + break; + } } } // We cannot return WVR_REDRAW otherwise Windows exhibits bugs where diff --git a/framelesshelper_windows.h b/framelesshelper_windows.h index a0f54be..68df4c7 100644 --- a/framelesshelper_windows.h +++ b/framelesshelper_windows.h @@ -77,7 +77,6 @@ #include #include #include -#include #ifndef WM_NCUAHDRAWCAPTION #define WM_NCUAHDRAWCAPTION (0x00AE) @@ -115,13 +114,13 @@ #define IsMaximized(window) (IsZoomed(window) != FALSE) #endif -[[maybe_unused]] constexpr UINT kAutoHideTaskbarThickness = 2; // The thickness of an auto-hide taskbar in pixels +[[maybe_unused]] static constexpr UINT kAutoHideTaskbarThickness = 2; // The thickness of an auto-hide taskbar in pixels -[[maybe_unused]] constexpr char kDwmRegistryKey[] = R"(HKEY_CURRENT_USER\Software\Microsoft\Windows\DWM)"; -[[maybe_unused]] constexpr char kPersonalizeRegistryKey[] = R"(HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize)"; +[[maybe_unused]] static constexpr char kDwmRegistryKey[] = R"(Software\Microsoft\Windows\DWM)"; +[[maybe_unused]] static constexpr char kPersonalizeRegistryKey[] = R"(Software\Microsoft\Windows\CurrentVersion\Themes\Personalize)"; -[[maybe_unused]] constexpr UINT kDefaultResizeBorderThicknessClassic = 4; -[[maybe_unused]] constexpr UINT kDefaultResizeBorderThicknessAero = 8; -[[maybe_unused]] constexpr UINT kDefaultCaptionHeight = 23; +[[maybe_unused]] static constexpr UINT kDefaultResizeBorderThicknessClassic = 4; +[[maybe_unused]] static constexpr UINT kDefaultResizeBorderThicknessAero = 8; +[[maybe_unused]] static constexpr UINT kDefaultCaptionHeight = 23; -[[maybe_unused]] constexpr DWORD _DWMWA_VISIBLE_FRAME_BORDER_THICKNESS = 37; +[[maybe_unused]] static constexpr DWORD _DWMWA_VISIBLE_FRAME_BORDER_THICKNESS = 37; diff --git a/lib.pro b/lib.pro index 53e45f0..3503849 100644 --- a/lib.pro +++ b/lib.pro @@ -2,7 +2,7 @@ TARGET = $$qtLibraryTarget(FramelessHelper) TEMPLATE = lib win32: DLLDESTDIR = $$OUT_PWD/bin else: unix: DESTDIR = $$OUT_PWD/bin -QT += gui-private +QT += core-private gui-private CONFIG += c++17 strict_c++ utf8_source warn_on DEFINES += \ QT_NO_CAST_FROM_ASCII \ @@ -32,6 +32,6 @@ win32 { SOURCES += \ utilities_win32.cpp \ framelesshelper_win32.cpp - LIBS += -luser32 -lshell32 -ldwmapi -lwinmm + LIBS += -luser32 -lshell32 RC_FILE = framelesshelper.rc } diff --git a/utilities_win32.cpp b/utilities_win32.cpp index 4504d50..f38bc25 100644 --- a/utilities_win32.cpp +++ b/utilities_win32.cpp @@ -24,7 +24,8 @@ #include "utilities.h" #include -#include +#include +#include #if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)) #include #else @@ -49,10 +50,12 @@ FRAMELESSHELPER_BEGIN_NAMESPACE return QPointF(static_cast(nativePos.x), static_cast(nativePos.y)); } -[[nodiscard]] static inline bool isWin10RS1OrGreater() +[[nodiscard]] static inline bool isWin10RS5OrGreater() { -#if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)) - static const bool result = (QOperatingSystemVersion::current() >= QOperatingSystemVersion(QOperatingSystemVersion::Windows, 10, 0, 14393)); +#if (QT_VERSION >= QT_VERSION_CHECK(6, 3, 0)) + static const bool result = (QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows10_1809); +#elif (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)) + static const bool result = (QOperatingSystemVersion::current() >= QOperatingSystemVersion(QOperatingSystemVersion::Windows, 10, 0, 17763)); #else static const bool result = (QSysInfo::WindowsVersion >= QSysInfo::WV_WINDOWS10); #endif @@ -61,7 +64,9 @@ FRAMELESSHELPER_BEGIN_NAMESPACE [[nodiscard]] static inline bool isWin1019H1OrGreater() { -#if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)) +#if (QT_VERSION >= QT_VERSION_CHECK(6, 3, 0)) + static const bool result = (QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows10_1903); +#elif (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)) static const bool result = (QOperatingSystemVersion::current() >= QOperatingSystemVersion(QOperatingSystemVersion::Windows, 10, 0, 18362)); #else static const bool result = (QSysInfo::WindowsVersion >= QSysInfo::WV_WINDOWS10); @@ -138,17 +143,23 @@ bool Utilities::isDwmCompositionAvailable() if (isWin8OrGreater()) { return true; } - BOOL enabled = FALSE; - const HRESULT hr = DwmIsCompositionEnabled(&enabled); - if (SUCCEEDED(hr)) { - return (enabled != FALSE); - } else { - qWarning() << __getSystemErrorMessage(QStringLiteral("DwmIsCompositionEnabled"), hr); - const QSettings registry(QString::fromUtf8(kDwmRegistryKey), QSettings::NativeFormat); - bool ok = false; - const DWORD value = registry.value(QStringLiteral("Composition"), 0).toUInt(&ok); - return (ok && (value != 0)); + const auto resultFromRegistry = []() -> bool { + QWinRegistryKey registry(HKEY_CURRENT_USER, QString::fromUtf8(kDwmRegistryKey)); + const auto result = registry.dwordValue(QStringLiteral("Composition")); + return (result.second && (result.first != 0)); + }; + static const auto pDwmIsCompositionEnabled = + reinterpret_cast(QSystemLibrary::resolve(QStringLiteral("dwmapi"), "DwmIsCompositionEnabled")); + if (!pDwmIsCompositionEnabled) { + return resultFromRegistry(); } + BOOL enabled = FALSE; + const HRESULT hr = pDwmIsCompositionEnabled(&enabled); + if (FAILED(hr)) { + qWarning() << __getSystemErrorMessage(QStringLiteral("DwmIsCompositionEnabled"), hr); + return resultFromRegistry(); + } + return (enabled != FALSE); } int Utilities::getSystemMetric(const QWindow *window, const SystemMetric metric, const bool dpiScale, const bool forceSystemValue) @@ -248,9 +259,14 @@ void Utilities::updateFrameMargins(const WId winId, const bool reset) if (!winId) { return; } + static const auto pDwmExtendFrameIntoClientArea = + reinterpret_cast(QSystemLibrary::resolve(QStringLiteral("dwmapi"), "DwmExtendFrameIntoClientArea")); + if (!pDwmExtendFrameIntoClientArea) { + return; + } const auto hwnd = reinterpret_cast(winId); const MARGINS margins = reset ? MARGINS{0, 0, 0, 0} : MARGINS{1, 1, 1, 1}; - const HRESULT hr = DwmExtendFrameIntoClientArea(hwnd, &margins); + const HRESULT hr = pDwmExtendFrameIntoClientArea(hwnd, &margins); if (FAILED(hr)) { qWarning() << __getSystemErrorMessage(QStringLiteral("DwmExtendFrameIntoClientArea"), hr); } @@ -303,14 +319,22 @@ QString Utilities::getSystemErrorMessage(const QString &function) QColor Utilities::getColorizationColor() { + static const auto pDwmGetColorizationColor = + reinterpret_cast(QSystemLibrary::resolve(QStringLiteral("dwmapi"), "DwmGetColorizationColor")); + if (!pDwmGetColorizationColor) { + return Qt::darkGray; + } DWORD color = 0; BOOL opaque = FALSE; - const HRESULT hr = DwmGetColorizationColor(&color, &opaque); + const HRESULT hr = pDwmGetColorizationColor(&color, &opaque); if (FAILED(hr)) { qWarning() << __getSystemErrorMessage(QStringLiteral("DwmGetColorizationColor"), hr); - const QSettings registry(QString::fromUtf8(kDwmRegistryKey), QSettings::NativeFormat); - bool ok = false; - color = registry.value(QStringLiteral("ColorizationColor"), 0).toUInt(&ok); + QWinRegistryKey registry(HKEY_CURRENT_USER, QString::fromUtf8(kDwmRegistryKey)); + const auto result = registry.dwordValue(QStringLiteral("ColorizationColor")); + if (!result.second) { + return Qt::darkGray; + } + return QColor::fromRgba(result.first); } return QColor::fromRgba(color); } @@ -324,9 +348,14 @@ int Utilities::getWindowVisibleFrameBorderThickness(const WId winId) if (!isWin10OrGreater()) { return 1; } + static const auto pDwmGetWindowAttribute = + reinterpret_cast(QSystemLibrary::resolve(QStringLiteral("dwmapi"), "DwmGetWindowAttribute")); + if (!pDwmGetWindowAttribute) { + return 1; + } const auto hWnd = reinterpret_cast(winId); UINT value = 0; - const HRESULT hr = DwmGetWindowAttribute(hWnd, _DWMWA_VISIBLE_FRAME_BORDER_THICKNESS, &value, sizeof(value)); + const HRESULT hr = pDwmGetWindowAttribute(hWnd, _DWMWA_VISIBLE_FRAME_BORDER_THICKNESS, &value, sizeof(value)); if (SUCCEEDED(hr)) { const QWindow *w = findWindow(winId); return static_cast(qRound(static_cast(value) / (w ? w->devicePixelRatio() : 1.0))); @@ -340,43 +369,25 @@ int Utilities::getWindowVisibleFrameBorderThickness(const WId winId) bool Utilities::shouldAppsUseDarkMode() { - if (!isWin10RS1OrGreater()) { + // The dark mode was introduced in Windows 10 1809. + if (!isWin10RS5OrGreater()) { return false; } const auto resultFromRegistry = []() -> bool { - const QSettings registry(QString::fromUtf8(kPersonalizeRegistryKey), QSettings::NativeFormat); - bool ok = false; - const DWORD value = registry.value(QStringLiteral("AppsUseLightTheme"), 0).toUInt(&ok); - return (ok && (value == 0)); + QWinRegistryKey registry(HKEY_CURRENT_USER, QString::fromUtf8(kPersonalizeRegistryKey)); + const auto result = registry.dwordValue(QStringLiteral("AppsUseLightTheme")); + return (result.second && (result.first == 0)); }; - // Starting from Windows 10 19H1, ShouldAppsUseDarkMode() always return "TRUE" + // Starting from Windows 10 1903, ShouldAppsUseDarkMode() always return "TRUE" // (actually, a random non-zero number at runtime), so we can't use it due to // this unreliability. In this case, we just simply read the user's setting from // the registry instead, it's not elegant but at least it works well. if (isWin1019H1OrGreater()) { return resultFromRegistry(); } else { - static bool tried = false; - using sig = BOOL(WINAPI *)(); - static sig func = nullptr; - if (!func) { - if (tried) { - return resultFromRegistry(); - } else { - tried = true; - const HMODULE dll = LoadLibraryExW(L"UxTheme.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); - if (!dll) { - qWarning() << getSystemErrorMessage(QStringLiteral("LoadLibraryExW")); - return resultFromRegistry(); - } - func = reinterpret_cast(GetProcAddress(dll, MAKEINTRESOURCEA(132))); - if (!func) { - qWarning() << getSystemErrorMessage(QStringLiteral("GetProcAddress")); - return resultFromRegistry(); - } - } - } - return (func() != FALSE); + static const auto pShouldAppsUseDarkMode = + reinterpret_cast(QSystemLibrary::resolve(QStringLiteral("uxtheme"), MAKEINTRESOURCEA(132))); + return (pShouldAppsUseDarkMode ? (pShouldAppsUseDarkMode() != FALSE) : resultFromRegistry()); } } @@ -386,12 +397,12 @@ ColorizationArea Utilities::getColorizationArea() return ColorizationArea::None; } const QString keyName = QStringLiteral("ColorPrevalence"); - const QSettings themeRegistry(QString::fromUtf8(kPersonalizeRegistryKey), QSettings::NativeFormat); - const DWORD themeValue = themeRegistry.value(keyName, 0).toUInt(); - const QSettings dwmRegistry(QString::fromUtf8(kDwmRegistryKey), QSettings::NativeFormat); - const DWORD dwmValue = dwmRegistry.value(keyName, 0).toUInt(); - const bool theme = (themeValue != 0); - const bool dwm = (dwmValue != 0); + QWinRegistryKey themeRegistry(HKEY_CURRENT_USER, QString::fromUtf8(kPersonalizeRegistryKey)); + const auto themeValue = themeRegistry.dwordValue(keyName); + QWinRegistryKey dwmRegistry(HKEY_CURRENT_USER, QString::fromUtf8(kDwmRegistryKey)); + const auto dwmValue = dwmRegistry.dwordValue(keyName); + const bool theme = themeValue.second && (themeValue.first != 0); + const bool dwm = dwmValue.second && (dwmValue.first != 0); if (theme && dwm) { return ColorizationArea::All; } else if (theme) {