Windows QPA: rework how we set dpi awareness

Qt6's minimum supported platform is Win10 1809, so it should be quite
safe to use DPI_AWARENESS_CONTEXT APIs because they were introduced
in Win10 1607.

This patch removes the use of the PROCESS_DPI_AWARENESS APIs because
they are old (introduced in Win8.1) and most importantly, they can't
handle the new PMv2 and GdiScaled awareness mode.

This refactor also fixed a bug: previously Qt is using GetProcessDpiAwareness()
to get the dpi awareness mode of the current process, however, that API can't
return PMv2, which means even if we are in PMv2 mode, it will still return PMv1
(I've confirmed that locally), and thus Qt is mishandling such cases.
Eg: when judging whether to enable non-client area dpi scaling or not.

Change-Id: I8a8946ba63c863f8c19c27998af2bac97db37ec7
Reviewed-by: Oliver Wolff <oliver.wolff@qt.io>
This commit is contained in:
Yuhang Zhao 2022-11-17 15:14:24 +08:00
parent 21baa76230
commit 5e0d9a077d
4 changed files with 141 additions and 84 deletions

View File

@ -150,13 +150,14 @@ enum WindowsEventType // Simplify event types
Q_DECLARE_MIXED_ENUM_OPERATORS(bool, WindowsEventTypeFlags, WindowsEventType);
Q_DECLARE_MIXED_ENUM_OPERATORS(bool, WindowsEventType, WindowsEventTypeFlags);
// Matches Process_DPI_Awareness (Windows 8.1 onwards), used for SetProcessDpiAwareness()
enum ProcessDpiAwareness
enum class DpiAwareness
{
ProcessDpiUnaware,
ProcessSystemDpiAware,
ProcessPerMonitorDpiAware,
ProcessPerMonitorV2DpiAware // Qt extension (not in Process_DPI_Awareness)
Invalid = -1,
Unaware,
System,
PerMonitor,
PerMonitorVersion2,
Unaware_GdiScaled
};
} // namespace QtWindows
@ -326,6 +327,10 @@ inline QtWindows::WindowsEventType windowsEventType(UINT message, WPARAM wParamI
return QtWindows::UnknownEvent;
}
#ifndef QT_NO_DEBUG_STREAM
extern QDebug operator<<(QDebug, QtWindows::DpiAwareness);
#endif
QT_END_NAMESPACE
#endif // QTWINDOWSGLOBAL_H

View File

@ -118,26 +118,6 @@ static inline bool sessionManagerInteractionBlocked()
static inline bool sessionManagerInteractionBlocked() { return false; }
#endif
static inline int windowDpiAwareness(HWND hwnd)
{
return static_cast<int>(GetAwarenessFromDpiAwarenessContext(GetWindowDpiAwarenessContext(hwnd)));
}
// Note: This only works within WM_NCCREATE
static bool enableNonClientDpiScaling(HWND hwnd)
{
bool result = false;
if (windowDpiAwareness(hwnd) == 2) {
result = EnableNonClientDpiScaling(hwnd) != FALSE;
if (!result) {
const DWORD errorCode = GetLastError();
qErrnoWarning(int(errorCode), "EnableNonClientDpiScaling() failed for HWND %p (%lu)",
hwnd, errorCode);
}
}
return result;
}
QWindowsContext *QWindowsContext::m_instance = nullptr;
/*!
@ -366,49 +346,117 @@ void QWindowsContext::setDetectAltGrModifier(bool a)
d->m_keyMapper.setDetectAltGrModifier(a);
}
int QWindowsContext::processDpiAwareness()
[[nodiscard]] static inline QtWindows::DpiAwareness
dpiAwarenessContextToQtDpiAwareness(DPI_AWARENESS_CONTEXT context)
{
PROCESS_DPI_AWARENESS result;
if (SUCCEEDED(GetProcessDpiAwareness(nullptr, &result))) {
return static_cast<int>(result);
}
return -1;
// IsValidDpiAwarenessContext() will handle the NULL pointer case.
if (!IsValidDpiAwarenessContext(context))
return QtWindows::DpiAwareness::Invalid;
if (AreDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED))
return QtWindows::DpiAwareness::Unaware_GdiScaled;
if (AreDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2))
return QtWindows::DpiAwareness::PerMonitorVersion2;
if (AreDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE))
return QtWindows::DpiAwareness::PerMonitor;
if (AreDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_SYSTEM_AWARE))
return QtWindows::DpiAwareness::System;
if (AreDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_UNAWARE))
return QtWindows::DpiAwareness::Unaware;
return QtWindows::DpiAwareness::Invalid;
}
void QWindowsContext::setProcessDpiAwareness(QtWindows::ProcessDpiAwareness dpiAwareness)
QtWindows::DpiAwareness QWindowsContext::windowDpiAwareness(HWND hwnd)
{
if (!hwnd)
return QtWindows::DpiAwareness::Invalid;
const auto context = GetWindowDpiAwarenessContext(hwnd);
return dpiAwarenessContextToQtDpiAwareness(context);
}
QtWindows::DpiAwareness QWindowsContext::processDpiAwareness()
{
// Although we have GetDpiAwarenessContextForProcess(), however,
// it's only available on Win10 1903+, which is a little higher
// than Qt's minimum supported version (1809), so we can't use it.
// Luckily, MS docs said GetThreadDpiAwarenessContext() will also
// return the default DPI_AWARENESS_CONTEXT for the process if
// SetThreadDpiAwarenessContext() was never called. So we can use
// it as an equivalent.
const auto context = GetThreadDpiAwarenessContext();
return dpiAwarenessContextToQtDpiAwareness(context);
}
[[nodiscard]] static inline DPI_AWARENESS_CONTEXT
qtDpiAwarenessToDpiAwarenessContext(QtWindows::DpiAwareness dpiAwareness)
{
switch (dpiAwareness) {
case QtWindows::DpiAwareness::Invalid:
return nullptr;
case QtWindows::DpiAwareness::Unaware:
return DPI_AWARENESS_CONTEXT_UNAWARE;
case QtWindows::DpiAwareness::System:
return DPI_AWARENESS_CONTEXT_SYSTEM_AWARE;
case QtWindows::DpiAwareness::PerMonitor:
return DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE;
case QtWindows::DpiAwareness::PerMonitorVersion2:
return DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2;
case QtWindows::DpiAwareness::Unaware_GdiScaled:
return DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED;
}
return nullptr;
}
#ifndef QT_NO_DEBUG_STREAM
QDebug operator<<(QDebug d, QtWindows::DpiAwareness dpiAwareness)
{
const QDebugStateSaver saver(d);
d.nospace().noquote() << "QtWindows::DpiAwareness::";
switch (dpiAwareness) {
case QtWindows::DpiAwareness::Invalid:
d.nospace().noquote() << "Invalid";
break;
case QtWindows::DpiAwareness::Unaware:
d.nospace().noquote() << "Unaware";
break;
case QtWindows::DpiAwareness::System:
d.nospace().noquote() << "System";
break;
case QtWindows::DpiAwareness::PerMonitor:
d.nospace().noquote() << "PerMonitor";
break;
case QtWindows::DpiAwareness::PerMonitorVersion2:
d.nospace().noquote() << "PerMonitorVersion2";
break;
case QtWindows::DpiAwareness::Unaware_GdiScaled:
d.nospace().noquote() << "Unaware_GdiScaled";
break;
}
return d;
}
#endif
bool QWindowsContext::setProcessDpiAwareness(QtWindows::DpiAwareness dpiAwareness)
{
qCDebug(lcQpaWindow) << __FUNCTION__ << dpiAwareness;
if (processDpiAwareness() == int(dpiAwareness))
return;
const HRESULT hr = SetProcessDpiAwareness(static_cast<PROCESS_DPI_AWARENESS>(dpiAwareness));
if (FAILED(hr)) {
qCWarning(lcQpaWindow).noquote().nospace() << "SetProcessDpiAwareness("
<< dpiAwareness << ") failed: " << QSystemError::windowsComString(hr) << ", using "
<< QWindowsContext::processDpiAwareness() << "\nQt's fallback DPI awareness is "
<< "PROCESS_DPI_AWARENESS. If you know what you are doing consider an override in qt.conf";
}
}
bool QWindowsContext::setProcessDpiV2Awareness()
{
qCDebug(lcQpaWindow) << __FUNCTION__;
auto dpiContext = GetThreadDpiAwarenessContext();
if (AreDpiAwarenessContextsEqual(dpiContext, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2))
if (processDpiAwareness() == dpiAwareness)
return true;
const BOOL ok = SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
if (!ok) {
const DWORD dwError = GetLastError();
qCWarning(lcQpaWindow).noquote().nospace()
<< "SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) failed: "
<< QSystemError::windowsComString(HRESULT(dwError)) << "\nQt's default DPI awareness "
<< "context is DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2. If you know what you "
<< "are doing you can overwrite this default using qt.conf "
<< "(https://doc.qt.io/qt-6/highdpi.html#configuring-windows)";
const auto context = qtDpiAwarenessToDpiAwarenessContext(dpiAwareness);
if (!IsValidDpiAwarenessContext(context)) {
qCWarning(lcQpaWindow) << dpiAwareness << "is not supported by current system.";
return false;
}
QWindowsContextPrivate::m_v2DpiAware = true;
if (!SetProcessDpiAwarenessContext(context)) {
qCWarning(lcQpaWindow).noquote().nospace()
<< "SetProcessDpiAwarenessContext() failed: "
<< QSystemError::windowsString()
<< "\nQt's default DPI awareness context is "
<< "DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2. If you know what you "
<< "are doing, you can overwrite this default using qt.conf "
<< "(https://doc.qt.io/qt-6/highdpi.html#configuring-windows).";
return false;
}
QWindowsContextPrivate::m_v2DpiAware
= processDpiAwareness() == QtWindows::DpiAwareness::PerMonitorVersion2;
return true;
}
@ -921,6 +969,21 @@ static inline bool isInputMessage(UINT m)
|| (m >= WM_KEYFIRST && m <= WM_KEYLAST);
}
// Note: This only works within WM_NCCREATE
static bool enableNonClientDpiScaling(HWND hwnd)
{
bool result = false;
if (QWindowsContext::windowDpiAwareness(hwnd) == QtWindows::DpiAwareness::PerMonitor) {
result = EnableNonClientDpiScaling(hwnd) != FALSE;
if (!result) {
const DWORD errorCode = GetLastError();
qErrnoWarning(int(errorCode), "EnableNonClientDpiScaling() failed for HWND %p (%lu)",
hwnd, errorCode);
}
}
return result;
}
/*!
\brief Main windows procedure registered for windows.

View File

@ -115,9 +115,10 @@ public:
QSharedPointer<QWindowCreationContext> windowCreationContext() const;
static void setTabletAbsoluteRange(int a);
void setProcessDpiAwareness(QtWindows::ProcessDpiAwareness dpiAwareness);
static int processDpiAwareness();
bool setProcessDpiV2Awareness();
bool setProcessDpiAwareness(QtWindows::DpiAwareness dpiAwareness);
static QtWindows::DpiAwareness processDpiAwareness();
static QtWindows::DpiAwareness windowDpiAwareness(HWND hwnd);
static bool isDarkMode();

View File

@ -152,7 +152,7 @@ bool parseIntOption(const QString &parameter,const QLatin1StringView &option,
const auto valueRef = QStringView{parameter}.right(valueLength);
const int value = valueRef.toInt(&ok);
if (ok) {
if (value >= minimumValue && value <= maximumValue)
if (value >= int(minimumValue) && value <= int(maximumValue))
*target = static_cast<IntType>(value);
else {
qWarning() << "Value" << value << "for option" << option << "out of range"
@ -169,7 +169,7 @@ using DarkModeHandling = QNativeInterface::Private::QWindowsApplication::DarkMod
static inline unsigned parseOptions(const QStringList &paramList,
int *tabletAbsoluteRange,
QtWindows::ProcessDpiAwareness *dpiAwareness,
QtWindows::DpiAwareness *dpiAwareness,
DarkModeHandling *darkModeHandling)
{
unsigned options = 0;
@ -200,7 +200,8 @@ static inline unsigned parseOptions(const QStringList &paramList,
options |= QWindowsIntegration::DontPassOsMouseEventsSynthesizedFromTouch;
} else if (parseIntOption(param, "verbose"_L1, 0, INT_MAX, &QWindowsContext::verbose)
|| parseIntOption(param, "tabletabsoluterange"_L1, 0, INT_MAX, tabletAbsoluteRange)
|| parseIntOption(param, "dpiawareness"_L1, QtWindows::ProcessDpiUnaware, QtWindows::ProcessPerMonitorV2DpiAware, dpiAwareness)) {
|| parseIntOption(param, "dpiawareness"_L1, QtWindows::DpiAwareness::Invalid,
QtWindows::DpiAwareness::PerMonitorVersion2, dpiAwareness)) {
} else if (param == u"menus=native") {
options |= QWindowsIntegration::AlwaysUseNativeMenus;
} else if (param == u"menus=none") {
@ -230,7 +231,7 @@ void QWindowsIntegrationPrivate::parseOptions(QWindowsIntegration *q, const QStr
static bool dpiAwarenessSet = false;
// Default to per-monitor-v2 awareness (if available)
QtWindows::ProcessDpiAwareness dpiAwareness = QtWindows::ProcessPerMonitorV2DpiAware;
QtWindows::DpiAwareness dpiAwareness = QtWindows::DpiAwareness::PerMonitorVersion2;
int tabletAbsoluteRange = -1;
DarkModeHandling darkModeHandling = DarkModeHandlingFlag::DarkModeWindowFrames
@ -249,22 +250,9 @@ void QWindowsIntegrationPrivate::parseOptions(QWindowsIntegration *q, const QStr
if (!dpiAwarenessSet) { // Set only once in case of repeated instantiations of QGuiApplication.
if (!QCoreApplication::testAttribute(Qt::AA_PluginApplication)) {
if (dpiAwareness == QtWindows::ProcessPerMonitorV2DpiAware) {
// DpiAwareV2 requires using new API
if (m_context.setProcessDpiV2Awareness()) {
qCDebug(lcQpaWindow, "DpiAwareness: DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2");
dpiAwarenessSet = true;
} else {
// fallback to old API
dpiAwareness = QtWindows::ProcessPerMonitorDpiAware;
}
}
if (!dpiAwarenessSet) {
m_context.setProcessDpiAwareness(dpiAwareness);
qCDebug(lcQpaWindow) << "DpiAwareness=" << dpiAwareness
<< "effective process DPI awareness=" << QWindowsContext::processDpiAwareness();
}
m_context.setProcessDpiAwareness(dpiAwareness);
qCDebug(lcQpaWindow) << "DpiAwareness=" << dpiAwareness
<< "effective process DPI awareness=" << QWindowsContext::processDpiAwareness();
}
dpiAwarenessSet = true;
}