Code refactor

1. Load dwmapi.dll and winmm.dll dynamically at runtime, to remove the dependency on those dlls
2. Use QSystemLibrary to load dlls, it can make the loading of system dlls safer, and avoid duplicating the code
3. Use QWinRegistryKey to access registry, no need to use QSettings in such scenarios
4. Adapt to new code when building against Qt 6.3 and newer.
5. Other minor tweaks.

Fixes: #94
This commit is contained in:
Yuhang Zhao 2022-01-05 11:00:31 +08:00
parent c3c5ef0d5d
commit 17601f386f
10 changed files with 150 additions and 146 deletions

View File

@ -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
)

View File

@ -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
}

View File

@ -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()

View File

@ -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()

View File

@ -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)

View File

@ -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()

View File

@ -26,6 +26,7 @@
#include <QtCore/qdebug.h>
#include <QtCore/qvariant.h>
#include <QtCore/qcoreapplication.h>
#include <QtCore/private/qsystemlibrary_p.h>
#include <QtGui/qwindow.h>
#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<MSG **>(message);
const auto msg = *static_cast<MSG **>(message);
#else
const auto msg = static_cast<LPMSG>(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<qreal>(m) / static_cast<qreal>(freq.QuadPart);
Sleep(static_cast<DWORD>(qRound(m_ms)));
if (timeEndPeriod(ms_granularity) != TIMERR_NOERROR) {
qWarning() << "timeEndPeriod() failed.";
break;
const QString winmm = QStringLiteral("winmm");
static const auto ptimeGetDevCaps =
reinterpret_cast<MMRESULT(WINAPI *)(LPTIMECAPS, UINT)>(QSystemLibrary::resolve(winmm, "timeGetDevCaps"));
static const auto ptimeBeginPeriod =
reinterpret_cast<MMRESULT(WINAPI *)(UINT)>(QSystemLibrary::resolve(winmm, "timeBeginPeriod"));
static const auto ptimeEndPeriod =
reinterpret_cast<MMRESULT(WINAPI *)(UINT)>(QSystemLibrary::resolve(winmm, "timeEndPeriod"));
static const auto pDwmGetCompositionTimingInfo =
reinterpret_cast<HRESULT(WINAPI *)(HWND, DWM_TIMING_INFO *)>(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<qreal>(m) / static_cast<qreal>(freq.QuadPart);
Sleep(static_cast<DWORD>(qRound(m_ms)));
if (ptimeEndPeriod(ms_granularity) != TIMERR_NOERROR) {
qWarning() << "timeEndPeriod() failed.";
break;
}
}
}
// We cannot return WVR_REDRAW otherwise Windows exhibits bugs where

View File

@ -77,7 +77,6 @@
#include <QtCore/qt_windows.h>
#include <shellapi.h>
#include <dwmapi.h>
#include <timeapi.h>
#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;

View File

@ -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
}

View File

@ -24,7 +24,8 @@
#include "utilities.h"
#include <QtCore/qdebug.h>
#include <QtCore/qsettings.h>
#include <QtCore/private/qwinregistry_p.h>
#include <QtCore/private/qsystemlibrary_p.h>
#if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0))
#include <QtCore/qoperatingsystemversion.h>
#else
@ -49,10 +50,12 @@ FRAMELESSHELPER_BEGIN_NAMESPACE
return QPointF(static_cast<qreal>(nativePos.x), static_cast<qreal>(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<HRESULT(WINAPI *)(BOOL *)>(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<HRESULT(WINAPI *)(HWND, const MARGINS *)>(QSystemLibrary::resolve(QStringLiteral("dwmapi"), "DwmExtendFrameIntoClientArea"));
if (!pDwmExtendFrameIntoClientArea) {
return;
}
const auto hwnd = reinterpret_cast<HWND>(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<HRESULT(WINAPI *)(DWORD *, BOOL *)>(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<HRESULT(WINAPI *)(HWND, DWORD, PVOID, DWORD)>(QSystemLibrary::resolve(QStringLiteral("dwmapi"), "DwmGetWindowAttribute"));
if (!pDwmGetWindowAttribute) {
return 1;
}
const auto hWnd = reinterpret_cast<HWND>(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<int>(qRound(static_cast<qreal>(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<sig>(GetProcAddress(dll, MAKEINTRESOURCEA(132)));
if (!func) {
qWarning() << getSystemErrorMessage(QStringLiteral("GetProcAddress"));
return resultFromRegistry();
}
}
}
return (func() != FALSE);
static const auto pShouldAppsUseDarkMode =
reinterpret_cast<BOOL(WINAPI *)(VOID)>(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) {