diff --git a/src/gui/kernel/qhighdpiscaling.cpp b/src/gui/kernel/qhighdpiscaling.cpp index 5bc3f11b3d..3a70e5acf7 100644 --- a/src/gui/kernel/qhighdpiscaling.cpp +++ b/src/gui/kernel/qhighdpiscaling.cpp @@ -49,10 +49,11 @@ #include #include +#include QT_BEGIN_NAMESPACE -Q_LOGGING_CATEGORY(lcScaling, "qt.scaling"); +Q_LOGGING_CATEGORY(lcHighDpi, "qt.highdpi"); #ifndef QT_NO_HIGHDPISCALING @@ -63,35 +64,38 @@ static const char scaleFactorRoundingPolicyEnvVar[] = "QT_SCALE_FACTOR_ROUNDING_ static const char dpiAdjustmentPolicyEnvVar[] = "QT_DPI_ADJUSTMENT_POLICY"; static const char usePhysicalDpiEnvVar[] = "QT_USE_PHYSICAL_DPI"; -// Per-screen scale factors for named screens set with QT_SCREEN_SCALE_FACTORS -// are stored here. Use a global hash to keep the factor across screen -// disconnect/connect cycles where the screen object may be deleted. -typedef QHash QScreenScaleFactorHash; -Q_GLOBAL_STATIC(QScreenScaleFactorHash, qNamedScreenScaleFactors); - -// Reads and interprets the given environment variable as a bool, -// returns the default value if not set. -static bool qEnvironmentVariableAsBool(const char *name, bool defaultValue) +static std::optional qEnvironmentVariableOptionalString(const char *name) { - bool ok = false; - int value = qEnvironmentVariableIntValue(name, &ok); - return ok ? value > 0 : defaultValue; + if (!qEnvironmentVariableIsSet(name)) + return std::nullopt; + + return std::optional(qEnvironmentVariable(name)); } -static inline qreal initialGlobalScaleFactor() +static std::optional qEnvironmentVariableOptionalByteArray(const char *name) { + if (!qEnvironmentVariableIsSet(name)) + return std::nullopt; - qreal result = 1; - if (qEnvironmentVariableIsSet(scaleFactorEnvVar)) { - bool ok; - const qreal f = qEnvironmentVariable(scaleFactorEnvVar).toDouble(&ok); - if (ok && f > 0) { - qCDebug(lcScaling) << "Apply " << scaleFactorEnvVar << f; - result = f; - } - } + return std::optional(qgetenv(name)); +} - return result; +static std::optional qEnvironmentVariableOptionalInt(const char *name) +{ + bool ok = false; + const int value = qEnvironmentVariableIntValue(name, &ok); + auto opt = ok ? std::optional(value) : std::nullopt; + return opt; +} + +static std::optional qEnvironmentVariableOptionalReal(const char *name) +{ + if (!qEnvironmentVariableIsSet(name)) + return std::nullopt; + + bool ok = false; + const qreal value = qEnvironmentVariable(name).toDouble(&ok); + return ok ? std::optional(value) : std::nullopt; } /*! @@ -275,35 +279,17 @@ bool QHighDpiScaling::m_usePlatformPluginDpi = false; // use scale factor based bool QHighDpiScaling::m_platformPluginDpiScalingActive = false; // platform plugin DPI gives a scale factor > 1 bool QHighDpiScaling::m_globalScalingActive = false; // global scale factor is active bool QHighDpiScaling::m_screenFactorSet = false; // QHighDpiScaling::setScreenFactor has been used - -/* - Initializes the QHighDpiScaling global variables. Called before the - platform plugin is created. -*/ - -static inline bool usePlatformPluginDpi() -{ - // Determine if we should set a scale factor based on the logical DPI - // reported by the platform plugin. - - bool enableEnvValueOk; - const int enableEnvValue = qEnvironmentVariableIntValue(enableHighDpiScalingEnvVar, &enableEnvValueOk); - if (enableEnvValueOk && enableEnvValue < 1) - return false; - - // Enable by default - return true; -} +bool QHighDpiScaling::m_usePhysicalDpi = false; +QHighDpiScaling::DpiAdjustmentPolicy QHighDpiScaling::m_dpiAdjustmentPolicy = QHighDpiScaling::DpiAdjustmentPolicy::Unset; +QString QHighDpiScaling::m_screenFactorsSpec; +QHash QHighDpiScaling::m_namedScreenScaleFactors; // Per-screen scale factors (screen name -> factor) qreal QHighDpiScaling::rawScaleFactor(const QPlatformScreen *screen) { - // Determine if physical DPI should be used - static const bool usePhysicalDpi = qEnvironmentVariableAsBool(usePhysicalDpiEnvVar, false); - // Calculate scale factor beased on platform screen DPI values qreal factor; QDpi platformBaseDpi = screen->logicalBaseDpi(); - if (usePhysicalDpi) { + if (QHighDpiScaling::m_usePhysicalDpi) { QSize sz = screen->geometry().size(); QSizeF psz = screen->physicalSize(); qreal platformPhysicalDpi = ((sz.height() / psz.height()) + (sz.width() / psz.width())) * qreal(25.4 * 0.5); @@ -387,32 +373,9 @@ qreal QHighDpiScaling::roundScaleFactor(qreal rawFactor) // sizes that are smaller than the ideal size, and opposite for rounding up. // Rounding down is then preferable since "small UI" is a more acceptable // high-DPI experience than "large UI". - static auto scaleFactorRoundingPolicy = Qt::HighDpiScaleFactorRoundingPolicy::Unset; - // Determine rounding policy - if (scaleFactorRoundingPolicy == Qt::HighDpiScaleFactorRoundingPolicy::Unset) { - // Check environment - if (qEnvironmentVariableIsSet(scaleFactorRoundingPolicyEnvVar)) { - QByteArray policyText = qgetenv(scaleFactorRoundingPolicyEnvVar); - auto policyEnumValue = lookupScaleFactorRoundingPolicy(policyText); - if (policyEnumValue != Qt::HighDpiScaleFactorRoundingPolicy::Unset) { - scaleFactorRoundingPolicy = policyEnumValue; - } else { - auto values = joinEnumValues(std::begin(scaleFactorRoundingPolicyLookup), - std::end(scaleFactorRoundingPolicyLookup)); - qWarning("Unknown scale factor rounding policy: %s. Supported values are: %s.", - policyText.constData(), values.constData()); - } - } - - // Check application object if no environment value was set. - if (scaleFactorRoundingPolicy == Qt::HighDpiScaleFactorRoundingPolicy::Unset) { - scaleFactorRoundingPolicy = QGuiApplication::highDpiScaleFactorRoundingPolicy(); - } else { - // Make application setting reflect environment - QGuiApplication::setHighDpiScaleFactorRoundingPolicy(scaleFactorRoundingPolicy); - } - } + Qt::HighDpiScaleFactorRoundingPolicy scaleFactorRoundingPolicy = + QGuiApplication::highDpiScaleFactorRoundingPolicy(); // Apply rounding policy. qreal roundedFactor = rawFactor; @@ -452,56 +415,108 @@ QDpi QHighDpiScaling::effectiveLogicalDpi(const QPlatformScreen *screen, qreal r // with the rest of the UI. The amount of out-of-synch-ness depends on how // well user code handles a non-standard DPI values, but since the // adjustment is small (typically +/- 48 max) this might be OK. - static auto dpiAdjustmentPolicy = DpiAdjustmentPolicy::Unset; - - // Determine adjustment policy. - if (dpiAdjustmentPolicy == DpiAdjustmentPolicy::Unset) { - if (qEnvironmentVariableIsSet(dpiAdjustmentPolicyEnvVar)) { - QByteArray policyText = qgetenv(dpiAdjustmentPolicyEnvVar); - auto policyEnumValue = lookupDpiAdjustmentPolicy(policyText); - if (policyEnumValue != DpiAdjustmentPolicy::Unset) { - dpiAdjustmentPolicy = policyEnumValue; - } else { - auto values = joinEnumValues(std::begin(dpiAdjustmentPolicyLookup), - std::end(dpiAdjustmentPolicyLookup)); - qWarning("Unknown DPI adjustment policy: %s. Supported values are: %s.", - policyText.constData(), values.constData()); - } - } - if (dpiAdjustmentPolicy == DpiAdjustmentPolicy::Unset) - dpiAdjustmentPolicy = DpiAdjustmentPolicy::UpOnly; - } // Apply adjustment policy. const QDpi baseDpi = screen->logicalBaseDpi(); const qreal dpiAdjustmentFactor = rawFactor / roundedFactor; // Return the base DPI for cases where there is no adjustment - if (dpiAdjustmentPolicy == DpiAdjustmentPolicy::Disabled) + if (QHighDpiScaling::m_dpiAdjustmentPolicy == DpiAdjustmentPolicy::Disabled) return baseDpi; - if (dpiAdjustmentPolicy == DpiAdjustmentPolicy::UpOnly && dpiAdjustmentFactor < 1) + if (QHighDpiScaling::m_dpiAdjustmentPolicy == DpiAdjustmentPolicy::UpOnly && dpiAdjustmentFactor < 1) return baseDpi; return QDpi(baseDpi.first * dpiAdjustmentFactor, baseDpi.second * dpiAdjustmentFactor); } +/* + Determine and apply global/initial configuration which do not depend on + having access to QScreen objects - this function is called before they + have been created. Screen-dependent configuration happens later in + updateHighDpiScaling(). +*/ void QHighDpiScaling::initHighDpiScaling() { - // Determine if there is a global scale factor set. - m_factor = initialGlobalScaleFactor(); + // Read environment variables + static const char* envDebugStr = "environment variable set:"; + std::optional envEnableHighDpiScaling = qEnvironmentVariableOptionalInt(enableHighDpiScalingEnvVar); + if (envEnableHighDpiScaling.has_value()) + qCDebug(lcHighDpi) << envDebugStr << enableHighDpiScalingEnvVar << envEnableHighDpiScaling.value(); + + std::optional envScaleFactor = qEnvironmentVariableOptionalReal(scaleFactorEnvVar); + if (envScaleFactor.has_value()) + qCDebug(lcHighDpi) << envDebugStr << scaleFactorEnvVar << envScaleFactor.value(); + + std::optional envScreenFactors = qEnvironmentVariableOptionalString(screenFactorsEnvVar); + if (envScreenFactors.has_value()) + qCDebug(lcHighDpi) << envDebugStr << screenFactorsEnvVar << envScreenFactors.value(); + + std::optional envUsePhysicalDpi = qEnvironmentVariableOptionalInt(usePhysicalDpiEnvVar); + if (envUsePhysicalDpi.has_value()) + qCDebug(lcHighDpi) << envDebugStr << usePhysicalDpiEnvVar << envUsePhysicalDpi.value(); + + std::optional envScaleFactorRoundingPolicy = qEnvironmentVariableOptionalByteArray(scaleFactorRoundingPolicyEnvVar); + if (envScaleFactorRoundingPolicy.has_value()) + qCDebug(lcHighDpi) << envDebugStr << scaleFactorRoundingPolicyEnvVar << envScaleFactorRoundingPolicy.value(); + + std::optional envDpiAdjustmentPolicy = qEnvironmentVariableOptionalByteArray(dpiAdjustmentPolicyEnvVar); + if (envDpiAdjustmentPolicy.has_value()) + qCDebug(lcHighDpi) << envDebugStr << dpiAdjustmentPolicyEnvVar << envDpiAdjustmentPolicy.value(); + + // High-dpi scaling is enabled by default; check for global disable. + m_usePlatformPluginDpi = envEnableHighDpiScaling.value_or(1) > 0; + m_platformPluginDpiScalingActive = false; // see updateHighDpiScaling() + + // Check for glabal scale factor (different from 1) + m_factor = envScaleFactor.value_or(qreal(1)); m_globalScalingActive = !qFuzzyCompare(m_factor, qreal(1)); - m_usePlatformPluginDpi = usePlatformPluginDpi(); + // Store the envScreenFactors string for later use. The string format + // supports using screen names, which means that screen DPI cannot + // be resolved at this point. + QHighDpiScaling::m_screenFactorsSpec = envScreenFactors.value_or(QString()); + m_namedScreenScaleFactors.clear(); - m_platformPluginDpiScalingActive = false; //set in updateHighDpiScaling below + m_usePhysicalDpi = envUsePhysicalDpi.value_or(0) > 0; + // Resolve HighDpiScaleFactorRoundingPolicy to QGuiApplication::highDpiScaleFactorRoundingPolicy + if (envScaleFactorRoundingPolicy.has_value()) { + QByteArray policyText = envScaleFactorRoundingPolicy.value(); + auto policyEnumValue = lookupScaleFactorRoundingPolicy(policyText); + if (policyEnumValue != Qt::HighDpiScaleFactorRoundingPolicy::Unset) { + QGuiApplication::setHighDpiScaleFactorRoundingPolicy(policyEnumValue); + } else { + auto values = joinEnumValues(std::begin(scaleFactorRoundingPolicyLookup), + std::end(scaleFactorRoundingPolicyLookup)); + qWarning("Unknown scale factor rounding policy: %s. Supported values are: %s.", + policyText.constData(), values.constData()); + } + } + + // Resolve DpiAdjustmentPolicy to m_dpiAdjustmentPolicy + if (envDpiAdjustmentPolicy.has_value()) { + QByteArray policyText = envScaleFactorRoundingPolicy.value(); + auto policyEnumValue = lookupDpiAdjustmentPolicy(policyText); + if (policyEnumValue != DpiAdjustmentPolicy::Unset) { + QHighDpiScaling::m_dpiAdjustmentPolicy = policyEnumValue; + } else { + auto values = joinEnumValues(std::begin(dpiAdjustmentPolicyLookup), + std::end(dpiAdjustmentPolicyLookup)); + qWarning("Unknown DPI adjustment policy: %s. Supported values are: %s.", + policyText.constData(), values.constData()); + } + } + + // Set initial active state m_active = m_globalScalingActive || m_usePlatformPluginDpi; } +/* + Update configuration based on available screens and screen properties. + This function may be called whenever the screen configuration changed. +*/ void QHighDpiScaling::updateHighDpiScaling() { - m_usePlatformPluginDpi = usePlatformPluginDpi(); - if (m_usePlatformPluginDpi && !m_platformPluginDpiScalingActive ) { const auto screens = QGuiApplication::screens(); for (QScreen *screen : screens) { @@ -511,10 +526,9 @@ void QHighDpiScaling::updateHighDpiScaling() } } } - if (qEnvironmentVariableIsSet(screenFactorsEnvVar)) { + if (!m_screenFactorsSpec.isNull()) { int i = 0; - const QString spec = qEnvironmentVariable(screenFactorsEnvVar); - const auto specs = QStringView{spec}.split(u';'); + const auto specs = QStringView{m_screenFactorsSpec}.split(u';'); for (const auto &spec : specs) { int equalsPos = spec.lastIndexOf(QLatin1Char('=')); qreal factor = 0; @@ -583,7 +597,7 @@ void QHighDpiScaling::setScreenFactor(QScreen *screen, qreal factor) if (name.isEmpty()) screen->setProperty(scaleFactorProperty, QVariant(factor)); else - qNamedScreenScaleFactors()->insert(name, factor); + QHighDpiScaling::m_namedScreenScaleFactors.insert(name, factor); // hack to force re-evaluation of screen geometry if (screen->handle()) @@ -630,8 +644,8 @@ qreal QHighDpiScaling::screenSubfactor(const QPlatformScreen *screen) } if (!screenPropertyUsed) { - auto byNameIt = qNamedScreenScaleFactors()->constFind(screen->name()); - if ((screenPropertyUsed = byNameIt != qNamedScreenScaleFactors()->cend())) + auto byNameIt = QHighDpiScaling::m_namedScreenScaleFactors.constFind(screen->name()); + if ((screenPropertyUsed = byNameIt != QHighDpiScaling::m_namedScreenScaleFactors.cend())) factor = *byNameIt; } } diff --git a/src/gui/kernel/qhighdpiscaling_p.h b/src/gui/kernel/qhighdpiscaling_p.h index 3a3983387c..e85a9b4bd6 100644 --- a/src/gui/kernel/qhighdpiscaling_p.h +++ b/src/gui/kernel/qhighdpiscaling_p.h @@ -64,7 +64,7 @@ QT_BEGIN_NAMESPACE -Q_DECLARE_LOGGING_CATEGORY(lcScaling); +Q_DECLARE_LOGGING_CATEGORY(lcHighDpi); class QScreen; class QPlatformScreen; @@ -138,6 +138,10 @@ private: static bool m_platformPluginDpiScalingActive; static bool m_globalScalingActive; static bool m_screenFactorSet; + static bool m_usePhysicalDpi; + static QString m_screenFactorsSpec; + static DpiAdjustmentPolicy m_dpiAdjustmentPolicy; + static QHash m_namedScreenScaleFactors; }; namespace QHighDpi { diff --git a/tests/auto/gui/kernel/qhighdpi/tst_qhighdpi.cpp b/tests/auto/gui/kernel/qhighdpi/tst_qhighdpi.cpp index 512209ab73..31a41eeff6 100644 --- a/tests/auto/gui/kernel/qhighdpi/tst_qhighdpi.cpp +++ b/tests/auto/gui/kernel/qhighdpi/tst_qhighdpi.cpp @@ -47,12 +47,16 @@ private: // helpers static void standardScreenDpiTestData(); private slots: void initTestCase(); + void cleanup(); void qhighdpiscaling_data(); void qhighdpiscaling(); void minimumDpr(); void noscreens(); void screenDpiAndDpr_data(); void screenDpiAndDpr(); + void environment_QT_SCALE_FACTOR(); + void environment_QT_SCREEN_SCALE_FACTORS(); + void environment_QT_USE_PHYSICAL_DPI(); void screenAt_data(); void screenAt(); void screenGeometry_data(); @@ -178,6 +182,15 @@ void tst_QHighDpi::initTestCase() #endif } +void tst_QHighDpi::cleanup() +{ + // Some test functions set environment variables. Unset them here, + // in order to avoid getting confusing follow-on errors on test failures. + qunsetenv("QT_SCALE_FACTOR"); + qunsetenv("QT_SCREEN_SCALE_FACTORS"); + qunsetenv("QT_USE_PHYSICAL_DPI"); +} + void tst_QHighDpi::qhighdpiscaling_data() { standardScreenDpiTestData(); @@ -224,6 +237,69 @@ void tst_QHighDpi::screenDpiAndDpr() } } +void tst_QHighDpi::environment_QT_SCALE_FACTOR() +{ + qreal factor = 3.1415; + qputenv("QT_SCALE_FACTOR", QByteArray::number(factor)); + + QList dpiValues { 96, 144, 192 }; + std::unique_ptr app(createStandardOffscreenApp(dpiValues)); + int i = 0; + for (QScreen *screen : app->screens()) { + // Verify that QT_SCALE_FACTOR applies as a multiplicative factor. + qreal expextedDpr = (dpiValues[i] / standardBaseDpi) * factor; + ++i; + QCOMPARE(screen->devicePixelRatio(), expextedDpr); + QCOMPARE(screen->logicalDotsPerInch(), 96); + QWindow window(screen); + QCOMPARE(window.devicePixelRatio(), expextedDpr); + } +} + +void tst_QHighDpi::environment_QT_SCREEN_SCALE_FACTORS() +{ + qreal factors[] = {1, 1.5, 2}; + qputenv("QT_SCREEN_SCALE_FACTORS", "1;1.5;2"); + + QList dpiValues { 192, 216, 240 }; + std::unique_ptr app(createStandardOffscreenApp(dpiValues)); + int i = 0; + for (QScreen *screen : app->screens()) { + qreal expextedDpr = factors[i]; + ++i; + // Verify that setting QT_SCREEN_SCALE_FACTORS overrides the from-dpi DPR + QCOMPARE(screen->devicePixelRatio(), expextedDpr); + QCOMPARE(screen->logicalDotsPerInch(), 96); + QWindow window(screen); + QCOMPARE(window.devicePixelRatio(), expextedDpr); + } +} + +void tst_QHighDpi::environment_QT_USE_PHYSICAL_DPI() +{ + qputenv("QT_USE_PHYSICAL_DPI", "1"); + + QList dpiValues { 96, 144, 192 }; + std::unique_ptr app(createStandardOffscreenApp(dpiValues)); + + // Verify that the device pixel ratio is computed as physicalDpi / baseDpi. + // (which in practice uses physicalSize since this is what QPlatformScreen provides) + + // The default QPlatformScreen::physicalSize() implementation (which QOffscreenScreen + // currerently uses) assumes a default DPI of 100 and calculates a fake physical size + // based on that value. Use DPI 100 here as well: if you have changed the default value + // in QPlatformScreen and get a test failure then update the value below. + const qreal platformScreenDefualtDpi = 100; + qreal expextedDpr = (platformScreenDefualtDpi / qreal(standardBaseDpi)); + + for (QScreen *screen : app->screens()) { + QCOMPARE(screen->devicePixelRatio(), expextedDpr); + QCOMPARE(screen->logicalDotsPerInch(), 96); + QWindow window(screen); + QCOMPARE(window.devicePixelRatio(), expextedDpr); + } +} + void tst_QHighDpi::minimumDpr() { QList dpiValues { 40, 60, 95 };