win: add more dpi hacks & fix some bugs

Signed-off-by: Yuhang Zhao <2546789017@qq.com>
This commit is contained in:
Yuhang Zhao 2022-11-09 13:22:58 +08:00
parent b88ac1591d
commit 8c35eb9b70
3 changed files with 256 additions and 16 deletions

View File

@ -35,6 +35,8 @@
#include "winverhelper_p.h" #include "winverhelper_p.h"
#include "framelesshelper_windows.h" #include "framelesshelper_windows.h"
EXTERN_C BOOL WINAPI EnableChildWindowDpiMessage2(const HWND hWnd, const BOOL fEnable);
FRAMELESSHELPER_BEGIN_NAMESPACE FRAMELESSHELPER_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(lcFramelessHelperWin, "wangwenx190.framelesshelper.core.impl.win") Q_LOGGING_CATEGORY(lcFramelessHelperWin, "wangwenx190.framelesshelper.core.impl.win")
@ -73,6 +75,7 @@ FRAMELESSHELPER_STRING_CONSTANT(TrackMouseEvent)
FRAMELESSHELPER_STRING_CONSTANT(FindWindowW) FRAMELESSHELPER_STRING_CONSTANT(FindWindowW)
FRAMELESSHELPER_STRING_CONSTANT(UnregisterClassW) FRAMELESSHELPER_STRING_CONSTANT(UnregisterClassW)
FRAMELESSHELPER_STRING_CONSTANT(DestroyWindow) FRAMELESSHELPER_STRING_CONSTANT(DestroyWindow)
FRAMELESSHELPER_STRING_CONSTANT(EnableChildWindowDpiMessage)
[[maybe_unused]] static constexpr const char kFallbackTitleBarErrorMessage[] = [[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" "FramelessHelper is unable to create the fallback title bar window, and thus the snap layout feature will be disabled"
" unconditionally. You can ignore this error and continue running your application, nothing else will be affected, " " unconditionally. You can ignore this error and continue running your application, nothing else will be affected, "
@ -97,6 +100,17 @@ struct Win32Helper
Q_GLOBAL_STATIC(Win32Helper, g_win32Helper) Q_GLOBAL_STATIC(Win32Helper, g_win32Helper)
[[nodiscard]] static inline QString hwnd2str(const WId windowId)
{
return FRAMELESSHELPER_STRING_LITERAL("0x")
+ QString::number(windowId, 16).toUpper();
}
[[nodiscard]] static inline QString hwnd2str(const HWND hwnd)
{
return hwnd2str(reinterpret_cast<WId>(hwnd));
}
[[nodiscard]] static inline LRESULT CALLBACK FallbackTitleBarWindowProc [[nodiscard]] static inline LRESULT CALLBACK FallbackTitleBarWindowProc
(const HWND hWnd, const UINT uMsg, const WPARAM wParam, const LPARAM lParam) (const HWND hWnd, const UINT uMsg, const WPARAM wParam, const LPARAM lParam)
{ {
@ -508,6 +522,9 @@ void FramelessHelperWin::addWindow(const SystemParameters &params)
qApp->installNativeEventFilter(g_win32Helper()->nativeEventFilter.data()); qApp->installNativeEventFilter(g_win32Helper()->nativeEventFilter.data());
} }
g_win32Helper()->mutex.unlock(); g_win32Helper()->mutex.unlock();
DEBUG.noquote() << "The DPI of window" << hwnd2str(windowId) << "is: QDpi("
<< Utils::getWindowDpi(windowId, true) << ','
<< Utils::getWindowDpi(windowId, false) << ").";
// Some Qt internals have to be corrected. // Some Qt internals have to be corrected.
Utils::maybeFixupQtInternals(windowId); Utils::maybeFixupQtInternals(windowId);
// Qt maintains a frame margin internally, we need to update it accordingly // Qt maintains a frame margin internally, we need to update it accordingly
@ -519,6 +536,18 @@ void FramelessHelperWin::addWindow(const SystemParameters &params)
Utils::updateWindowFrameMargins(windowId, false); Utils::updateWindowFrameMargins(windowId, false);
// Tell DWM we don't use the window icon/caption/sysmenu, don't draw them. // Tell DWM we don't use the window icon/caption/sysmenu, don't draw them.
Utils::hideOriginalTitleBarElements(windowId); Utils::hideOriginalTitleBarElements(windowId);
// We don't need this hack on Win10 1607 and newer, the PMv2 DPI awareness
// mode will take care of it for us by default.
if (WindowsVersionHelper::isWin10OrGreater()
&& !WindowsVersionHelper::isWin10RS1OrGreater()) {
// Without this hack, child windows can't get DPI change messages,
// which means only top level windows can scale to the correct size.
if (EnableChildWindowDpiMessage2(reinterpret_cast<HWND>(windowId), TRUE) == FALSE) {
if (GetLastError() != ERROR_CALL_NOT_IMPLEMENTED) {
WARNING << Utils::getSystemErrorMessage(kEnableChildWindowDpiMessage);
}
}
}
if (WindowsVersionHelper::isWin10RS1OrGreater()) { if (WindowsVersionHelper::isWin10RS1OrGreater()) {
// Tell DWM we may need dark theme non-client area (title bar & frame border). // Tell DWM we may need dark theme non-client area (title bar & frame border).
FramelessHelper::Core::setApplicationOSThemeAware(); FramelessHelper::Core::setApplicationOSThemeAware();
@ -1096,6 +1125,10 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
} break; } break;
#endif #endif
case WM_DPICHANGED: { case WM_DPICHANGED: {
const UINT dpiX = LOWORD(wParam);
const UINT dpiY = HIWORD(wParam);
DEBUG.noquote() << "New DPI for window" << hwnd2str(hWnd)
<< ": QDpi(" << dpiX << ',' << dpiY << ").";
// Sync the internal window frame margins with the latest DPI. // Sync the internal window frame margins with the latest DPI.
Utils::updateInternalWindowFrameMargins(data.params.getWindowHandle(), true); Utils::updateInternalWindowFrameMargins(data.params.getWindowHandle(), true);
// For some unknown reason, Qt sometimes won't re-paint the window contents after // For some unknown reason, Qt sometimes won't re-paint the window contents after

View File

@ -87,6 +87,11 @@ _GetSystemMetricsForDpi(
_In_ UINT dpi _In_ UINT dpi
); );
UINT WINAPI
_GetWindowDPI(
_In_ HWND hWnd
);
UINT WINAPI UINT WINAPI
_GetDpiForWindow( _GetDpiForWindow(
_In_ HWND hWnd _In_ HWND hWnd
@ -149,6 +154,32 @@ _AreDpiAwarenessContextsEqual(
_In_ _DPI_AWARENESS_CONTEXT dpiContextB _In_ _DPI_AWARENESS_CONTEXT dpiContextB
); );
BOOL WINAPI
_EnableChildWindowDpiMessage(
_In_ HWND hWnd,
_In_ BOOL fEnable
);
BOOL WINAPI
_EnablePerMonitorDialogScaling(
VOID
);
int WINAPI
_GetDpiMetrics(
_In_ int nIndex,
_In_ UINT dpi
);
BOOL WINAPI
_AdjustWindowRectExForDpi(
_Inout_ LPRECT lpRect,
_In_ DWORD dwStyle,
_In_ BOOL bMenu,
_In_ DWORD dwExStyle,
_In_ UINT dpi
);
EXTERN_C_END EXTERN_C_END
EXTERN_C [[nodiscard]] FRAMELESSHELPER_CORE_API BOOL WINAPI EXTERN_C [[nodiscard]] FRAMELESSHELPER_CORE_API BOOL WINAPI
@ -395,6 +426,150 @@ IsDarkModeAllowedForApp(VOID)
return pIsDarkModeAllowedForApp(); return pIsDarkModeAllowedForApp();
} }
EXTERN_C [[nodiscard]] FRAMELESSHELPER_CORE_API BOOL WINAPI
EnableChildWindowDpiMessage2(const HWND hWnd, const BOOL fEnable)
{
using EnableChildWindowDpiMessagePtr = decltype(&::_EnableChildWindowDpiMessage);
static const auto pEnableChildWindowDpiMessage = []() -> EnableChildWindowDpiMessagePtr {
FRAMELESSHELPER_USE_NAMESPACE
FRAMELESSHELPER_STRING_CONSTANT(user32)
FRAMELESSHELPER_STRING_CONSTANT(EnableChildWindowDpiMessage)
// EnableChildWindowDpiMessage() was once a public API, so we can load it by name,
// but it got removed in some later SDK versions, so we can't link to it directly.
// I haven't check the accurate time point of its removal.
if (const auto pFunc = reinterpret_cast<EnableChildWindowDpiMessagePtr>(
SysApiLoader::resolve(kuser32, kEnableChildWindowDpiMessage))) {
return pFunc;
}
// EnableChildWindowDpiMessage() was a private API when first introduced.
if (const auto pFunc = reinterpret_cast<EnableChildWindowDpiMessagePtr>(
SysApiLoader::resolve(kuser32, MAKEINTRESOURCEA(2704)))) {
return pFunc;
}
return nullptr;
}();
if (!pEnableChildWindowDpiMessage) {
SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
return FALSE;
}
return pEnableChildWindowDpiMessage(hWnd, fEnable);
}
EXTERN_C [[nodiscard]] FRAMELESSHELPER_CORE_API BOOL WINAPI
EnablePerMonitorDialogScaling2(VOID)
{
using EnablePerMonitorDialogScalingPtr = decltype(&::_EnablePerMonitorDialogScaling);
static const auto pEnablePerMonitorDialogScaling = []() -> EnablePerMonitorDialogScalingPtr {
FRAMELESSHELPER_USE_NAMESPACE
FRAMELESSHELPER_STRING_CONSTANT(user32)
FRAMELESSHELPER_STRING_CONSTANT(EnablePerMonitorDialogScaling)
// EnablePerMonitorDialogScaling() was once a public API, so we can load it by name,
// but it got removed in some later SDK versions, so we can't link to it directly.
// I haven't check the accurate time point of its removal.
if (const auto pFunc = reinterpret_cast<EnablePerMonitorDialogScalingPtr>(
SysApiLoader::resolve(kuser32, kEnablePerMonitorDialogScaling))) {
return pFunc;
}
// EnablePerMonitorDialogScaling() was a private API when first introduced.
if (const auto pFunc = reinterpret_cast<EnablePerMonitorDialogScalingPtr>(
SysApiLoader::resolve(kuser32, MAKEINTRESOURCEA(2577)))) {
return pFunc;
}
return nullptr;
}();
if (!pEnablePerMonitorDialogScaling) {
SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
return FALSE;
}
return pEnablePerMonitorDialogScaling();
}
EXTERN_C [[nodiscard]] FRAMELESSHELPER_CORE_API UINT WINAPI
GetDpiForWindow2(const HWND hWnd)
{
using GetDpiForWindowPtr = decltype(&::_GetDpiForWindow);
static const auto pGetDpiForWindow = []() -> GetDpiForWindowPtr {
FRAMELESSHELPER_USE_NAMESPACE
FRAMELESSHELPER_STRING_CONSTANT(user32)
FRAMELESSHELPER_STRING_CONSTANT(GetDpiForWindow)
if (const auto pFunc = reinterpret_cast<GetDpiForWindowPtr>(
SysApiLoader::resolve(kuser32, kGetDpiForWindow))) {
return pFunc;
}
// GetDpiForWindow() was named "GetWindowDPI" before made public.
FRAMELESSHELPER_STRING_CONSTANT(GetWindowDPI)
if (const auto pFunc = reinterpret_cast<GetDpiForWindowPtr>(
SysApiLoader::resolve(kuser32, kGetWindowDPI))) {
return pFunc;
}
// GetDpiForWindow() was a private API when first introduced.
if (const auto pFunc = reinterpret_cast<GetDpiForWindowPtr>(
SysApiLoader::resolve(kuser32, MAKEINTRESOURCEA(2707)))) {
return pFunc;
}
return nullptr;
}();
if (!pGetDpiForWindow) {
SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
return 0;
}
return pGetDpiForWindow(hWnd);
}
EXTERN_C [[nodiscard]] FRAMELESSHELPER_CORE_API int WINAPI
GetSystemMetricsForDpi2(const int nIndex, const UINT dpi)
{
using GetSystemMetricsForDpiPtr = decltype(&::_GetSystemMetricsForDpi);
static const auto pGetSystemMetricsForDpi = []() -> GetSystemMetricsForDpiPtr {
FRAMELESSHELPER_USE_NAMESPACE
FRAMELESSHELPER_STRING_CONSTANT(user32)
FRAMELESSHELPER_STRING_CONSTANT(GetSystemMetricsForDpi)
if (const auto pFunc = reinterpret_cast<GetSystemMetricsForDpiPtr>(
SysApiLoader::resolve(kuser32, kGetSystemMetricsForDpi))) {
return pFunc;
}
// GetSystemMetricsForDpi() was named "GetDpiMetrics" before made public.
FRAMELESSHELPER_STRING_CONSTANT(GetDpiMetrics)
if (const auto pFunc = reinterpret_cast<GetSystemMetricsForDpiPtr>(
SysApiLoader::resolve(kuser32, kGetDpiMetrics))) {
return pFunc;
}
return nullptr;
}();
if (!pGetSystemMetricsForDpi) {
SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
return 0;
}
return pGetSystemMetricsForDpi(nIndex, dpi);
}
EXTERN_C [[nodiscard]] FRAMELESSHELPER_CORE_API BOOL WINAPI
AdjustWindowRectExForDpi2(LPRECT lpRect, const DWORD dwStyle,
const BOOL bMenu, const DWORD dwExStyle, const UINT dpi)
{
using AdjustWindowRectExForDpiPtr = decltype(&::_AdjustWindowRectExForDpi);
static const auto pAdjustWindowRectExForDpi = []() -> AdjustWindowRectExForDpiPtr {
FRAMELESSHELPER_USE_NAMESPACE
FRAMELESSHELPER_STRING_CONSTANT(user32)
FRAMELESSHELPER_STRING_CONSTANT(AdjustWindowRectExForDpi)
if (const auto pFunc = reinterpret_cast<AdjustWindowRectExForDpiPtr>(
SysApiLoader::resolve(kuser32, kAdjustWindowRectExForDpi))) {
return pFunc;
}
// AdjustWindowRectExForDpi() was a private API when first introduced.
if (const auto pFunc = reinterpret_cast<AdjustWindowRectExForDpiPtr>(
SysApiLoader::resolve(kuser32, MAKEINTRESOURCEA(2580)))) {
return pFunc;
}
return nullptr;
}();
if (!pAdjustWindowRectExForDpi) {
SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
return FALSE;
}
return pAdjustWindowRectExForDpi(lpRect, dwStyle, bMenu, dwExStyle, dpi);
}
Q_DECLARE_METATYPE(QMargins) Q_DECLARE_METATYPE(QMargins)
FRAMELESSHELPER_BEGIN_NAMESPACE FRAMELESSHELPER_BEGIN_NAMESPACE
@ -510,6 +685,11 @@ FRAMELESSHELPER_STRING_CONSTANT(GetCurrentProcess)
FRAMELESSHELPER_STRING_CONSTANT(GetProcessDpiAwareness) FRAMELESSHELPER_STRING_CONSTANT(GetProcessDpiAwareness)
FRAMELESSHELPER_STRING_CONSTANT(IsProcessDPIAware) FRAMELESSHELPER_STRING_CONSTANT(IsProcessDPIAware)
FRAMELESSHELPER_STRING_CONSTANT(AreDpiAwarenessContextsEqual) FRAMELESSHELPER_STRING_CONSTANT(AreDpiAwarenessContextsEqual)
FRAMELESSHELPER_STRING_CONSTANT(GetWindowDPI)
FRAMELESSHELPER_STRING_CONSTANT(AdjustWindowRectExForDpi)
FRAMELESSHELPER_STRING_CONSTANT(GetDpiMetrics)
FRAMELESSHELPER_STRING_CONSTANT(EnablePerMonitorDialogScaling)
FRAMELESSHELPER_STRING_CONSTANT(EnableChildWindowDpiMessage)
struct Win32UtilsHelperData struct Win32UtilsHelperData
{ {
@ -661,13 +841,16 @@ struct SYSTEM_METRIC
return 0; return 0;
} }
const UINT realDpi = Utils::getWindowDpi(windowId, horizontal); const UINT realDpi = Utils::getWindowDpi(windowId, horizontal);
if (API_USER_AVAILABLE(GetSystemMetricsForDpi)) { {
const UINT dpi = (scaled ? realDpi : USER_DEFAULT_SCREEN_DPI); const UINT dpi = (scaled ? realDpi : USER_DEFAULT_SCREEN_DPI);
return API_CALL_FUNCTION4(GetSystemMetricsForDpi, index, dpi); if (const int result = GetSystemMetricsForDpi2(index, dpi)) {
} else { return result;
const qreal dpr = (scaled ? qreal(1) : (qreal(realDpi) / qreal(USER_DEFAULT_SCREEN_DPI))); }
return qRound(qreal(GetSystemMetrics(index)) / dpr);
} }
// GetSystemMetrics() will always return a scaled value, so if we want to get an unscaled
// one, we have to calculate it ourself.
const qreal dpr = (scaled ? qreal(1) : (qreal(realDpi) / qreal(USER_DEFAULT_SCREEN_DPI)));
return qRound(qreal(GetSystemMetrics(index)) / dpr);
} }
[[maybe_unused]] [[nodiscard]] static inline [[maybe_unused]] [[nodiscard]] static inline
@ -1301,11 +1484,13 @@ quint32 Utils::getWindowDpi(const WId windowId, const bool horizontal)
return USER_DEFAULT_SCREEN_DPI; return USER_DEFAULT_SCREEN_DPI;
} }
const auto hwnd = reinterpret_cast<HWND>(windowId); const auto hwnd = reinterpret_cast<HWND>(windowId);
if (API_USER_AVAILABLE(GetDpiForWindow)) { {
const UINT dpi = API_CALL_FUNCTION4(GetDpiForWindow, hwnd); if (const UINT dpi = GetDpiForWindow2(hwnd)) {
if (dpi > 0) {
return dpi; return dpi;
} else { }
// ERROR_CALL_NOT_IMPLEMENTED: the function is not available on
// current platform, not an error.
if (GetLastError() != ERROR_CALL_NOT_IMPLEMENTED) {
WARNING << getSystemErrorMessage(kGetDpiForWindow); WARNING << getSystemErrorMessage(kGetDpiForWindow);
} }
} }
@ -1542,6 +1727,9 @@ bool Utils::isWindowFrameBorderVisible()
{ {
static const bool result = []() -> bool { static const bool result = []() -> bool {
const FramelessConfig * const config = FramelessConfig::instance(); const FramelessConfig * const config = FramelessConfig::instance();
if (config->isSet(Option::UseCrossPlatformQtImplementation)) {
return false;
}
if (config->isSet(Option::ForceShowWindowFrameBorder)) { if (config->isSet(Option::ForceShowWindowFrameBorder)) {
return true; return true;
} }
@ -1662,6 +1850,18 @@ void Utils::setAeroSnappingEnabled(const WId windowId, const bool enable)
void Utils::tryToEnableHighestDpiAwarenessLevel() void Utils::tryToEnableHighestDpiAwarenessLevel()
{ {
// We don't need this hack when running on Win10 1607 and newer, the PMv2
// DPI awareness mode will take care of it for us by default.
if (WindowsVersionHelper::isWin10OrGreater()
&& !WindowsVersionHelper::isWin10RS1OrGreater()) {
// This function need to be called before any dialogs are created, so
// to be safe we call it here.
if (EnablePerMonitorDialogScaling2() == FALSE) {
if (GetLastError() != ERROR_CALL_NOT_IMPLEMENTED) {
WARNING << getSystemErrorMessage(kEnablePerMonitorDialogScaling);
}
}
}
bool isHighestAlready = false; bool isHighestAlready = false;
const DpiAwareness currentAwareness = getDpiAwarenessForCurrentProcess(&isHighestAlready); const DpiAwareness currentAwareness = getDpiAwarenessForCurrentProcess(&isHighestAlready);
DEBUG << "Current DPI awareness mode:" << currentAwareness; DEBUG << "Current DPI awareness mode:" << currentAwareness;
@ -1679,7 +1879,7 @@ void Utils::tryToEnableHighestDpiAwarenessLevel()
} }
const DWORD dwError = GetLastError(); const DWORD dwError = GetLastError();
// "ERROR_ACCESS_DENIED" means set externally (mostly due to manifest file). // "ERROR_ACCESS_DENIED" means set externally (mostly due to manifest file).
// Any attempt to change the DPI awareness level through API will always fail, // Any attempt to change the DPI awareness mode through API will always fail,
// so we treat this situation as succeeded. // so we treat this situation as succeeded.
if (dwError == ERROR_ACCESS_DENIED) { if (dwError == ERROR_ACCESS_DENIED) {
DEBUG << kDpiNoAccessErrorMessage; DEBUG << kDpiNoAccessErrorMessage;
@ -1720,7 +1920,7 @@ void Utils::tryToEnableHighestDpiAwarenessLevel()
return true; return true;
} }
// "E_ACCESSDENIED" means set externally (mostly due to manifest file). // "E_ACCESSDENIED" means set externally (mostly due to manifest file).
// Any attempt to change the DPI awareness level through API will always fail, // Any attempt to change the DPI awareness mode through API will always fail,
// so we treat this situation as succeeded. // so we treat this situation as succeeded.
if (hr == E_ACCESSDENIED) { if (hr == E_ACCESSDENIED) {
DEBUG << kDpiNoAccessErrorMessage; DEBUG << kDpiNoAccessErrorMessage;
@ -1827,8 +2027,8 @@ bool Utils::shouldAppsUseDarkMode_windows()
// it works well. // it works well.
// However, reverse engineering of Win11's Task Manager reveals that Microsoft still // However, reverse engineering of Win11's Task Manager reveals that Microsoft still
// uses this function internally to determine the system theme, and the Task Manager // uses this function internally to determine the system theme, and the Task Manager
// can correctly respond to the theme change event indeed. Is it fixed silently // can correctly respond to the theme change event indeed. But strangely, I've checked
// in some unknown Windows versions? To be checked. // that it's still broken on Win11 22H2. What's going on here?
if (WindowsVersionHelper::isWin10RS5OrGreater() if (WindowsVersionHelper::isWin10RS5OrGreater()
&& !WindowsVersionHelper::isWin1019H1OrGreater()) { && !WindowsVersionHelper::isWin1019H1OrGreater()) {
return (ShouldAppsUseDarkMode() != FALSE); return (ShouldAppsUseDarkMode() != FALSE);

View File

@ -262,9 +262,16 @@ void WidgetsSharedHelper::handleScreenChanged(QScreen *screen)
void WidgetsSharedHelper::updateContentsMargins() void WidgetsSharedHelper::updateContentsMargins()
{ {
#ifdef Q_OS_WINDOWS #ifdef Q_OS_WINDOWS
m_targetWidget->setContentsMargins(0, const auto margins = [this]() -> QMargins {
((Utils::windowStatesToWindowState(m_targetWidget->windowState()) == Qt::WindowNoState) if (!Utils::isWindowFrameBorderVisible() || WindowsVersionHelper::isWin11OrGreater()) {
? kDefaultWindowFrameBorderThickness : 0), 0, 0); return {};
}
if (Utils::windowStatesToWindowState(m_targetWidget->windowState()) != Qt::WindowNoState) {
return {};
}
return {0, kDefaultWindowFrameBorderThickness, 0, 0};
}();
m_targetWidget->setContentsMargins(margins);
#endif #endif
} }