summaryrefslogtreecommitdiffstats
path: root/src/gui/kernel/qhighdpiscaling.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui/kernel/qhighdpiscaling.cpp')
-rw-r--r--src/gui/kernel/qhighdpiscaling.cpp234
1 files changed, 210 insertions, 24 deletions
diff --git a/src/gui/kernel/qhighdpiscaling.cpp b/src/gui/kernel/qhighdpiscaling.cpp
index 95790b96a9..7ffbfbe1e5 100644
--- a/src/gui/kernel/qhighdpiscaling.cpp
+++ b/src/gui/kernel/qhighdpiscaling.cpp
@@ -46,6 +46,9 @@
#include <private/qguiapplication_p.h>
#include <QtCore/qdebug.h>
+#include <QtCore/qmetaobject.h>
+
+#include <algorithm>
QT_BEGIN_NAMESPACE
@@ -56,6 +59,18 @@ static const char legacyDevicePixelEnvVar[] = "QT_DEVICE_PIXEL_RATIO";
static const char scaleFactorEnvVar[] = "QT_SCALE_FACTOR";
static const char autoScreenEnvVar[] = "QT_AUTO_SCREEN_SCALE_FACTOR";
static const char screenFactorsEnvVar[] = "QT_SCREEN_SCALE_FACTORS";
+static const char scaleFactorRoundingPolicyEnvVar[] = "QT_SCALE_FACTOR_ROUNDING_POLICY";
+static const char dpiAdjustmentPolicyEnvVar[] = "QT_DPI_ADJUSTMENT_POLICY";
+static const char usePhysicalDpiEnvVar[] = "QT_USE_PHYSICAL_DPI";
+
+// 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)
+{
+ bool ok = false;
+ int value = qEnvironmentVariableIntValue(name, &ok);
+ return ok ? value > 0 : defaultValue;
+}
static inline qreal initialGlobalScaleFactor()
{
@@ -249,6 +264,191 @@ static inline bool usePixelDensity()
qgetenv(legacyDevicePixelEnvVar).compare("auto", Qt::CaseInsensitive) == 0);
}
+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) {
+ qreal platformPhysicalDpi = screen->screen()->physicalDotsPerInch();
+ factor = qreal(platformPhysicalDpi) / qreal(platformBaseDpi.first);
+ } else {
+ QDpi platformLogicalDpi = screen->logicalDpi();
+ factor = qreal(platformLogicalDpi.first) / qreal(platformBaseDpi.first);
+ }
+
+ return factor;
+}
+
+template <class EnumType>
+struct EnumLookup
+{
+ const char *name;
+ EnumType value;
+};
+
+template <class EnumType>
+static bool operator==(const EnumLookup<EnumType> &e1, const EnumLookup<EnumType> &e2)
+{
+ return qstricmp(e1.name, e2.name) == 0;
+}
+
+template <class EnumType>
+static QByteArray joinEnumValues(const EnumLookup<EnumType> *i1, const EnumLookup<EnumType> *i2)
+{
+ QByteArray result;
+ for (; i1 < i2; ++i1) {
+ if (!result.isEmpty())
+ result += QByteArrayLiteral(", ");
+ result += i1->name;
+ }
+ return result;
+}
+
+using ScaleFactorRoundingPolicyLookup = EnumLookup<QHighDpiScaling::HighDpiScaleFactorRoundingPolicy>;
+
+static const ScaleFactorRoundingPolicyLookup scaleFactorRoundingPolicyLookup[] =
+{
+ {"Round", QHighDpiScaling::HighDpiScaleFactorRoundingPolicy::Round},
+ {"Ceil", QHighDpiScaling::HighDpiScaleFactorRoundingPolicy::Ceil},
+ {"Floor", QHighDpiScaling::HighDpiScaleFactorRoundingPolicy::Floor},
+ {"RoundPreferFloor", QHighDpiScaling::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor},
+ {"PassThrough", QHighDpiScaling::HighDpiScaleFactorRoundingPolicy::PassThrough}
+};
+
+static QHighDpiScaling::HighDpiScaleFactorRoundingPolicy
+ lookupScaleFactorRoundingPolicy(const QByteArray &v)
+{
+ auto end = std::end(scaleFactorRoundingPolicyLookup);
+ auto it = std::find(std::begin(scaleFactorRoundingPolicyLookup), end,
+ ScaleFactorRoundingPolicyLookup{v.constData(), QHighDpiScaling::HighDpiScaleFactorRoundingPolicy::Unset});
+ return it != end ? it->value : QHighDpiScaling::HighDpiScaleFactorRoundingPolicy::Unset;
+}
+
+using DpiAdjustmentPolicyLookup = EnumLookup<QHighDpiScaling::DpiAdjustmentPolicy>;
+
+static const DpiAdjustmentPolicyLookup dpiAdjustmentPolicyLookup[] =
+{
+ {"AdjustDpi", QHighDpiScaling::DpiAdjustmentPolicy::Enabled},
+ {"DontAdjustDpi", QHighDpiScaling::DpiAdjustmentPolicy::Disabled},
+ {"AdjustUpOnly", QHighDpiScaling::DpiAdjustmentPolicy::UpOnly}
+};
+
+static QHighDpiScaling::DpiAdjustmentPolicy
+ lookupDpiAdjustmentPolicy(const QByteArray &v)
+{
+ auto end = std::end(dpiAdjustmentPolicyLookup);
+ auto it = std::find(std::begin(dpiAdjustmentPolicyLookup), end,
+ DpiAdjustmentPolicyLookup{v.constData(), QHighDpiScaling::DpiAdjustmentPolicy::Unset});
+ return it != end ? it->value : QHighDpiScaling::DpiAdjustmentPolicy::Unset;
+}
+
+qreal QHighDpiScaling::roundScaleFactor(qreal rawFactor)
+{
+ // Apply scale factor rounding policy. Using mathematically correct rounding
+ // may not give the most desirable visual results, especially for
+ // critical fractions like .5. In general, rounding down results in visual
+ // 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 = HighDpiScaleFactorRoundingPolicy::Unset;
+
+ // Determine rounding policy
+ if (scaleFactorRoundingPolicy == HighDpiScaleFactorRoundingPolicy::Unset) {
+ // Check environment
+ if (qEnvironmentVariableIsSet(scaleFactorRoundingPolicyEnvVar)) {
+ QByteArray policyText = qgetenv(scaleFactorRoundingPolicyEnvVar);
+ auto policyEnumValue = lookupScaleFactorRoundingPolicy(policyText);
+ if (policyEnumValue != 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());
+ }
+ } else {
+ // Set default policy if no environment variable is set.
+ scaleFactorRoundingPolicy = HighDpiScaleFactorRoundingPolicy::RoundPreferFloor;
+ }
+ }
+
+ // Apply rounding policy.
+ qreal roundedFactor = rawFactor;
+ switch (scaleFactorRoundingPolicy) {
+ case HighDpiScaleFactorRoundingPolicy::Round:
+ roundedFactor = qRound(rawFactor);
+ break;
+ case HighDpiScaleFactorRoundingPolicy::Ceil:
+ roundedFactor = qCeil(rawFactor);
+ break;
+ case HighDpiScaleFactorRoundingPolicy::Floor:
+ roundedFactor = qFloor(rawFactor);
+ break;
+ case HighDpiScaleFactorRoundingPolicy::RoundPreferFloor:
+ // Round up for .75 and higher. This favors "small UI" over "large UI".
+ roundedFactor = rawFactor - qFloor(rawFactor) < 0.75
+ ? qFloor(rawFactor) : qCeil(rawFactor);
+ break;
+ case HighDpiScaleFactorRoundingPolicy::PassThrough:
+ case HighDpiScaleFactorRoundingPolicy::Unset:
+ break;
+ }
+
+ // Don't round down to to zero; clamp the minimum (rounded) factor to 1.
+ // This is not a common case but can happen if a display reports a very
+ // low DPI.
+ if (scaleFactorRoundingPolicy != HighDpiScaleFactorRoundingPolicy::PassThrough)
+ roundedFactor = qMax(roundedFactor, qreal(1));
+
+ return roundedFactor;
+}
+
+QDpi QHighDpiScaling::effectiveLogicalDpi(const QPlatformScreen *screen, qreal rawFactor, qreal roundedFactor)
+{
+ // Apply DPI adjustment policy, if needed. If enabled this will change the
+ // reported logical DPI to account for the difference between the rounded
+ // scale factor and the actual scale factor. The effect is that text size
+ // will be correct for the screen dpi, but may be (slightly) out of sync
+ // 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)
+ return baseDpi;
+ if (dpiAdjustmentPolicy == DpiAdjustmentPolicy::UpOnly && dpiAdjustmentFactor < 1)
+ return baseDpi;
+
+ return QDpi(baseDpi.first * dpiAdjustmentFactor, baseDpi.second * dpiAdjustmentFactor);
+}
+
void QHighDpiScaling::initHighDpiScaling()
{
// Determine if there is a global scale factor set.
@@ -259,8 +459,6 @@ void QHighDpiScaling::initHighDpiScaling()
m_pixelDensityScalingActive = false; //set in updateHighDpiScaling below
- // we update m_active in updateHighDpiScaling, but while we create the
- // screens, we have to assume that m_usePixelDensity implies scaling
m_active = m_globalScalingActive || m_usePixelDensity;
}
@@ -312,7 +510,7 @@ void QHighDpiScaling::updateHighDpiScaling()
++i;
}
}
- m_active = m_globalScalingActive || m_screenFactorSet || m_pixelDensityScalingActive;
+ m_active = m_globalScalingActive || m_usePixelDensity;
}
/*
@@ -413,22 +611,8 @@ qreal QHighDpiScaling::screenSubfactor(const QPlatformScreen *screen)
{
qreal factor = qreal(1.0);
if (screen) {
- if (m_usePixelDensity) {
- qreal pixelDensity = screen->pixelDensity();
-
- // Pixel density reported by the screen is sometimes not precise enough,
- // so recalculate it: divide px (physical pixels) by dp (device-independent pixels)
- // for both width and height, and then use the average if it is different from
- // the one initially reported by the screen
- QRect screenGeometry = screen->geometry();
- qreal wFactor = qreal(screenGeometry.width()) / qRound(screenGeometry.width() / pixelDensity);
- qreal hFactor = qreal(screenGeometry.height()) / qRound(screenGeometry.height() / pixelDensity);
- qreal averageDensity = (wFactor + hFactor) / 2;
- if (!qFuzzyCompare(pixelDensity, averageDensity))
- pixelDensity = averageDensity;
-
- factor *= pixelDensity;
- }
+ if (m_usePixelDensity)
+ factor *= roundScaleFactor(rawScaleFactor(screen));
if (m_screenFactorSet) {
QVariant screenFactor = screen->screen()->property(scaleFactorProperty);
if (screenFactor.isValid())
@@ -441,13 +625,15 @@ qreal QHighDpiScaling::screenSubfactor(const QPlatformScreen *screen)
QDpi QHighDpiScaling::logicalDpi(const QScreen *screen)
{
// (Note: m_active test is performed at call site.)
- if (!screen)
+ if (!screen || !screen->handle())
return QDpi(96, 96);
- qreal platformScreenfactor = screenSubfactor(screen->handle());
- QDpi platformScreenDpi = screen->handle()->logicalDpi();
- return QDpi(platformScreenDpi.first / platformScreenfactor,
- platformScreenDpi.second / platformScreenfactor);
+ if (!m_usePixelDensity)
+ return screen->handle()->logicalDpi();
+
+ const qreal scaleFactor = rawScaleFactor(screen->handle());
+ const qreal roundedScaleFactor = roundScaleFactor(scaleFactor);
+ return effectiveLogicalDpi(screen->handle(), scaleFactor, roundedScaleFactor);
}
QHighDpiScaling::ScaleAndOrigin QHighDpiScaling::scaleAndOrigin(const QPlatformScreen *platformScreen, QPoint *nativePosition)