Revision control
++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++ #include "qhighdpiscaling_p.h"
++ #include "qguiapplication.h"
++ #include "qscreen.h"
++ #include "qplatformintegration.h"
++ #include "qplatformwindow.h"
++ #include "private/qscreen_p.h"
++ #include <private/qguiapplication_p.h>
++
++ #include <QtCore/qdebug.h>
++ #include <QtCore/qmetaobject.h>
++
++ #include <algorithm>
++ #include <optional>
++
++ QT_BEGIN_NAMESPACE
++
++ Q_LOGGING_CATEGORY(lcHighDpi, "qt.highdpi");
++
++ #ifndef QT_NO_HIGHDPISCALING
++
++ static const char enableHighDpiScalingEnvVar[] = "QT_ENABLE_HIGHDPI_SCALING";
++ static const char scaleFactorEnvVar[] = "QT_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";
++
++ [[maybe_unused]]
++ static std::optional<QString> qEnvironmentVariableOptionalString(const char *name)
++ {
++ QString value = qEnvironmentVariable(name);
++ return value.isNull() ? std::nullopt : std::optional(std::move(value));
++ }
++
++ static std::optional<QByteArray> qEnvironmentVariableOptionalByteArray(const char *name)
++ {
++ QByteArray value = qgetenv(name);
++ return value.isNull() ? std::nullopt : std::optional(std::move(value));
++ }
++
++ static std::optional<qreal> qEnvironmentVariableOptionalReal(const char *name)
++ {
++ const QByteArray val = qgetenv(name);
++ if (val.isNull())
++ return std::nullopt;
++
++ bool ok = false;
++ const qreal value = val.toDouble(&ok);
++ return ok ? std::optional(value) : std::nullopt;
++ }
++
++ /*!
++ \class QHighDpiScaling
++ \since 5.6
++ \internal
++ \preliminary
++ \ingroup qpa
++
++ \brief Collection of utility functions for UI scaling.
++
++ QHighDpiScaling implements utility functions for high-dpi scaling for use
++ on operating systems that provide limited support for native scaling, such
++ as Windows, X11, and Android. In addition this functionality can be used
++ for simulation and testing purposes.
++
++ The functions support scaling between the device independent coordinate
++ system used by Qt applications and the native coordinate system used by
++ the platform plugins. Intended usage locations are the low level / platform
++ plugin interfacing parts of QtGui, for example the QWindow, QScreen and
++ QWindowSystemInterface implementation.
++
++ There are now up to three active coordinate systems in Qt:
++
++ ---------------------------------------------------
++ | Application Device Independent Pixels | devicePixelRatio
++ | Qt Widgets | =
++ | Qt Gui |
++ |---------------------------------------------------| Qt Scale Factor
++ | Qt Gui QPlatform* Native Pixels | *
++ | Qt platform plugin |
++ |---------------------------------------------------| OS Scale Factor
++ | Display Device Pixels |
++ | (Graphics Buffers) |
++ -----------------------------------------------------
++
++ This is an simplification and shows the main coordinate system. All layers
++ may work with device pixels in specific cases: OpenGL, creating the backing
++ store, and QPixmap management. The "Native Pixels" coordinate system is
++ internal to Qt and should not be exposed to Qt users: Seen from the outside
++ there are only two coordinate systems: device independent pixels and device
++ pixels.
++
++ The devicePixelRatio seen by applications is the product of the Qt scale
++ factor and the OS scale factor (see QWindow::devicePixelRatio()). The value
++ of the scale factors may be 1, in which case two or more of the coordinate
++ systems are equivalent. Platforms that (may) have an OS scale factor include
++ macOS, iOS, Wayland, and Web(Assembly).
++
++ Note that the API implemented in this file do use the OS scale factor, and
++ is used for converting between device independent and native pixels only.
++
++ Configuration Examples:
++
++ 'Classic': Device Independent Pixels = Native Pixels = Device Pixels
++ --------------------------------------------------- devicePixelRatio: 1
++ | Application / Qt Gui 100 x 100 |
++ | | Qt Scale Factor: 1
++ | Qt Platform / OS 100 x 100 |
++ | | OS Scale Factor: 1
++ | Display 100 x 100 |
++ -----------------------------------------------------
++
++ '2x Apple Device': Device Independent Pixels = Native Pixels
++ --------------------------------------------------- devicePixelRatio: 2
++ | Application / Qt Gui 100 x 100 |
++ | | Qt Scale Factor: 1
++ | Qt Platform / OS 100 x 100 |
++ |---------------------------------------------------| OS Scale Factor: 2
++ | Display 200 x 200 |
++ -----------------------------------------------------
++
++ 'Windows at 200%': Native Pixels = Device Pixels
++ --------------------------------------------------- devicePixelRatio: 2
++ | Application / Qt Gui 100 x 100 |
++ |---------------------------------------------------| Qt Scale Factor: 2
++ | Qt Platform / OS 200 x 200 |
++ | | OS Scale Factor: 1
++ | Display 200 x 200 |
++ -----------------------------------------------------
++
++ * Configuration
++
++ - Enabling: In Qt 6, high-dpi scaling (the functionality implemented in this file)
++ is always enabled. The Qt scale factor value is typically determined by the
++ QPlatformScreen implementation - see below.
++
++ There is one environment variable based opt-out option: set QT_ENABLE_HIGHDPI_SCALING=0.
++ Keep in mind that this does not affect the OS scale factor, which is controlled by
++ the operating system.
++
++ - Qt scale factor value: The Qt scale factor is the product of the screen scale
++ factor and the global scale factor, which are independently either set or determined
++ by the platform plugin. Several APIs are offered for this, targeting both developers
++ and end users. All scale factors are of type qreal.
++
++ 1) Per-screen scale factors
++
++ Per-screen scale factors are computed based on logical DPI provided by
++ by the platform plugin.
++
++ The platform plugin implements DPI accessor functions:
++ QDpi QPlatformScreen::logicalDpi()
++ QDpi QPlatformScreen::logicalBaseDpi()
++
++ QHighDpiScaling then computes the per-screen scale factor as follows:
++
++ factor = logicalDpi / logicalBaseDpi
++
++ Alternatively, QT_SCREEN_SCALE_FACTORS can be used to set the screen
++ scale factors.
++
++ 2) The global scale factor
++
++ The QT_SCALE_FACTOR environment variable can be used to set a global scale
++ factor which applies to all application windows. This allows developing and
++ testing at any DPR, independently of available hardware and without changing
++ global desktop settings.
++
++ - Rounding
++
++ Qt 6 does not round scale factors by default. Qt 5 rounds the screen scale factor
++ to the nearest integer (except for Qt on Android which does not round).
++
++ The rounding policy can be set by the application, or on the environment:
++
++ Application (C++): QGuiApplication::setHighDpiScaleFactorRoundingPolicy()
++ User (environment): QT_SCALE_FACTOR_ROUNDING_POLICY
++
++ Note that the OS scale factor, and global scale factors set with QT_SCALE_FACTOR
++ are never rounded by Qt.
++
++ * C++ API Overview
++
++ - Coordinate Conversion ("scaling")
++
++ The QHighDpi namespace provides several functions for converting geometry
++ between the device independent and native coordinate systems. These should
++ be used when calling "QPlatform*" API from QtGui. Callers are responsible
++ for selecting a function variant based on geometry type:
++
++ Type From Native To Native
++ local : QHighDpi::fromNativeLocalPosition() QHighDpi::toNativeLocalPosition()
++ global (screen) : QHighDpi::fromNativeGlobalPosition() QHighDpi::toNativeGlobalPosition()
++ QWindow::geometry() : QHighDpi::fromNativeWindowGeometry() QHighDpi::toNativeWindowGeometry()
++ sizes, margins, etc : QHighDpi::fromNativePixels() QHighDpi::toNativePixels()
++
++ The conversion functions take two arguments; the geometry and a context:
++
++ QSize nativeSize = toNativePixels(deviceIndependentSize, window);
++
++ The context is usually a QWindow instance, but can also be a QScreen instance,
++ or the corresponding QPlatform classes.
++
++ - Activation
++
++ QHighDpiScaling::isActive() returns true iff
++ Qt high-dpi scaling is enabled (e.g. with AA_EnableHighDpiScaling) AND
++ there is a Qt scale factor != 1
++
++ (the value of the OS scale factor does not affect this API)
++
++ - Calling QtGui from the platform plugins
++
++ Platform plugin code should be careful about calling QtGui geometry accessor
++ functions like geometry():
++
++ QRect r = window->geometry();
++
++ In this case the returned geometry is in the wrong coordinate system (device independent
++ instead of native pixels). Fix this by adding a conversion call:
++
++ QRect r = QHighDpi::toNativeWindowGeometry(window->geometry());
++
++ (Also consider if the call to QtGui is really needed - prefer calling QPlatform* API.)
++ */
++
++ qreal QHighDpiScaling::m_factor = 1.0;
++ bool QHighDpiScaling::m_active = false; //"overall active" - is there any scale factor set.
++ bool QHighDpiScaling::m_usePlatformPluginDpi = false; // use scale factor based on platform plugin DPI
++ 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
++ bool QHighDpiScaling::m_usePhysicalDpi = false;
++ QVector<QHighDpiScaling::ScreenFactor> QHighDpiScaling::m_screenFactors;
++ QHighDpiScaling::DpiAdjustmentPolicy QHighDpiScaling::m_dpiAdjustmentPolicy = QHighDpiScaling::DpiAdjustmentPolicy::Unset;
++ QHash<QString, qreal> QHighDpiScaling::m_namedScreenScaleFactors; // Per-screen scale factors (screen name -> factor)
++
++ qreal QHighDpiScaling::rawScaleFactor(const QPlatformScreen *screen)
++ {
++ // Calculate scale factor beased on platform screen DPI values
++ qreal factor;
++ QDpi platformBaseDpi = screen->logicalBaseDpi();
++ 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);
++ factor = qRound(platformPhysicalDpi) / qreal(platformBaseDpi.first);
++ } else {
++ const QDpi platformLogicalDpi = QPlatformScreen::overrideDpi(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<Qt::HighDpiScaleFactorRoundingPolicy>;
++
++ static const ScaleFactorRoundingPolicyLookup scaleFactorRoundingPolicyLookup[] =
++ {
++ {"Round", Qt::HighDpiScaleFactorRoundingPolicy::Round},
++ {"Ceil", Qt::HighDpiScaleFactorRoundingPolicy::Ceil},
++ {"Floor", Qt::HighDpiScaleFactorRoundingPolicy::Floor},
++ {"RoundPreferFloor", Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor},
++ {"PassThrough", Qt::HighDpiScaleFactorRoundingPolicy::PassThrough}
++ };
++
++ static Qt::HighDpiScaleFactorRoundingPolicy
++ lookupScaleFactorRoundingPolicy(const QByteArray &v)
++ {
++ auto end = std::end(scaleFactorRoundingPolicyLookup);
++ auto it = std::find(std::begin(scaleFactorRoundingPolicyLookup), end,
++ ScaleFactorRoundingPolicyLookup{v.constData(), Qt::HighDpiScaleFactorRoundingPolicy::Unset});
++ return it != end ? it->value : Qt::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".
++
++ Qt::HighDpiScaleFactorRoundingPolicy scaleFactorRoundingPolicy =
++ QGuiApplication::highDpiScaleFactorRoundingPolicy();
++
++ // Apply rounding policy.
++ qreal roundedFactor = rawFactor;
++ switch (scaleFactorRoundingPolicy) {
++ case Qt::HighDpiScaleFactorRoundingPolicy::Round:
++ roundedFactor = qRound(rawFactor);
++ break;
++ case Qt::HighDpiScaleFactorRoundingPolicy::Ceil:
++ roundedFactor = qCeil(rawFactor);
++ break;
++ case Qt::HighDpiScaleFactorRoundingPolicy::Floor:
++ roundedFactor = qFloor(rawFactor);
++ break;
++ case Qt::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 Qt::HighDpiScaleFactorRoundingPolicy::PassThrough:
++ case Qt::HighDpiScaleFactorRoundingPolicy::Unset:
++ break;
++ }
++
++ // Clamp the minimum factor to 1. Qt does not currently render
++ // correctly with factors less than 1.
++ 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.
++
++ // 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 (QHighDpiScaling::m_dpiAdjustmentPolicy == DpiAdjustmentPolicy::Disabled)
++ return baseDpi;
++ 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()
++ {
++ qCDebug(lcHighDpi) << "Initializing high-DPI scaling";
++
++ // Read environment variables
++ static const char* envDebugStr = "environment variable set:";
++ std::optional envEnableHighDpiScaling = qEnvironmentVariableIntegerValue(enableHighDpiScalingEnvVar);
++ if (envEnableHighDpiScaling.has_value())
++ qCDebug(lcHighDpi) << envDebugStr << enableHighDpiScalingEnvVar << envEnableHighDpiScaling.value();
++
++ std::optional<qreal> envScaleFactor = qEnvironmentVariableOptionalReal(scaleFactorEnvVar);
++ if (envScaleFactor.has_value())
++ qCDebug(lcHighDpi) << envDebugStr << scaleFactorEnvVar << envScaleFactor.value();
++
++ const QString envScreenFactors = qEnvironmentVariable(screenFactorsEnvVar);
++ if (envScreenFactors.isNull())
++ qCDebug(lcHighDpi) << envDebugStr << screenFactorsEnvVar << envScreenFactors;
++
++ std::optional envUsePhysicalDpi = qEnvironmentVariableIntegerValue(usePhysicalDpiEnvVar);
++ if (envUsePhysicalDpi.has_value())
++ qCDebug(lcHighDpi) << envDebugStr << usePhysicalDpiEnvVar << envUsePhysicalDpi.value();
++
++ std::optional<QByteArray> envScaleFactorRoundingPolicy = qEnvironmentVariableOptionalByteArray(scaleFactorRoundingPolicyEnvVar);
++ if (envScaleFactorRoundingPolicy.has_value())
++ qCDebug(lcHighDpi) << envDebugStr << scaleFactorRoundingPolicyEnvVar << envScaleFactorRoundingPolicy.value();
++
++ std::optional<QByteArray> 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));
++
++ // 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.
++ m_screenFactors = parseScreenScaleFactorsSpec(envScreenFactors);
++ m_namedScreenScaleFactors.clear();
++
++ 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) {
++ // set directly to avoid setHighDpiScaleFactorRoundingPolicy() warning
++ QGuiApplicationPrivate::highDpiScaleFactorRoundingPolicy = 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 = envDpiAdjustmentPolicy.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;
++
++ qCDebug(lcHighDpi) << "Initialization done, high-DPI scaling is"
++ << (m_active ? "active" : "inactive");
++ }
++
++ /*
++ Update configuration based on available screens and screen properties.
++ This function may be called whenever the screen configuration changed.
++ */
++ void QHighDpiScaling::updateHighDpiScaling()
++ {
++ qCDebug(lcHighDpi) << "Updating high-DPI scaling";
++
++ // Apply screen factors from environment
++ if (m_screenFactors.size() > 0) {
++ qCDebug(lcHighDpi) << "Applying screen factors" << m_screenFactors;
++ int i = -1;
++ const auto screens = QGuiApplication::screens();
++ for (const auto &[name, rawFactor]: m_screenFactors) {
++ const qreal factor = roundScaleFactor(rawFactor);
++ ++i;
++ if (name.isNull()) {
++ if (i < screens.size())
++ setScreenFactor(screens.at(i), factor);
++ } else {
++ for (QScreen *screen : screens) {
++ if (screen->name() == name) {
++ setScreenFactor(screen, factor);
++ break;
++ }
++ }
++ }
++ }
++ }
++
++ // Check if any screens (now) has a scale factor != 1 and set
++ // m_platformPluginDpiScalingActive if so.
++ if (m_usePlatformPluginDpi && !m_platformPluginDpiScalingActive ) {
++ const auto screens = QGuiApplication::screens();
++ for (QScreen *screen : screens) {
++ if (!qFuzzyCompare(screenSubfactor(screen->handle()), qreal(1))) {
++ m_platformPluginDpiScalingActive = true;
++ break;
++ }
++ }
++ }
++
++ m_active = m_globalScalingActive || m_screenFactorSet || m_platformPluginDpiScalingActive;
++
++ qCDebug(lcHighDpi) << "Update done, high-DPI scaling is"
++ << (m_active ? "active" : "inactive");
++ }
++
++ /*
++ Sets the global scale factor which is applied to all windows.
++ */
++ void QHighDpiScaling::setGlobalFactor(qreal factor)
++ {
++ qCDebug(lcHighDpi) << "Setting global scale factor to" << factor;
++
++ if (qFuzzyCompare(factor, m_factor))
++ return;
++ if (!QGuiApplication::allWindows().isEmpty())
++ qWarning("QHighDpiScaling::setFactor: Should only be called when no windows exist.");
++
++ const auto screens = QGuiApplication::screens();
++
++ std::vector<QScreenPrivate::UpdateEmitter> updateEmitters;
++ for (QScreen *screen : screens)
++ updateEmitters.emplace_back(screen);
++
++ m_globalScalingActive = !qFuzzyCompare(factor, qreal(1));
++ m_factor = m_globalScalingActive ? factor : qreal(1);
++ m_active = m_globalScalingActive || m_screenFactorSet || m_platformPluginDpiScalingActive ;
++ for (QScreen *screen : screens)
++ screen->d_func()->updateGeometry();
++ }
++
++ static const char scaleFactorProperty[] = "_q_scaleFactor";
++
++ /*
++ Sets a per-screen scale factor.
++ */
++ void QHighDpiScaling::setScreenFactor(QScreen *screen, qreal factor)
++ {
++ qCDebug(lcHighDpi) << "Setting screen scale factor for" << screen << "to" << factor;
++
++ if (!qFuzzyCompare(factor, qreal(1))) {
++ m_screenFactorSet = true;
++ m_active = true;
++ }
++
++ QScreenPrivate::UpdateEmitter updateEmitter(screen);
++
++ // Prefer associating the factor with screen name over the object
++ // since the screen object may be deleted on screen disconnects.
++ const QString name = screen->name();
++ if (name.isEmpty())
++ screen->setProperty(scaleFactorProperty, QVariant(factor));
++ else
++ QHighDpiScaling::m_namedScreenScaleFactors.insert(name, factor);
++
++ screen->d_func()->updateGeometry();
++ }
++
++ QPoint QHighDpiScaling::mapPositionToNative(const QPoint &pos, const QPlatformScreen *platformScreen)
++ {
++ if (!platformScreen)
++ return pos;
++ const qreal scaleFactor = factor(platformScreen);
++ const QPoint topLeft = platformScreen->geometry().topLeft();
++ return (pos - topLeft) * scaleFactor + topLeft;
++ }
++
++ QPoint QHighDpiScaling::mapPositionFromNative(const QPoint &pos, const QPlatformScreen *platformScreen)
++ {
++ if (!platformScreen)
++ return pos;
++ const qreal scaleFactor = factor(platformScreen);
++ const QPoint topLeft = platformScreen->geometry().topLeft();
++ return (pos - topLeft) / scaleFactor + topLeft;
++ }
++
++ qreal QHighDpiScaling::screenSubfactor(const QPlatformScreen *screen)
++ {
++ auto factor = qreal(1.0);
++ if (!screen)
++ return factor;
++
++ // Unlike the other code where factors are combined by multiplication,
++ // factors from QT_SCREEN_SCALE_FACTORS takes precedence over the factor
++ // computed from platform plugin DPI. The rationale is that the user is
++ // setting the factor to override erroneous DPI values.
++ bool screenPropertyUsed = false;
++ if (m_screenFactorSet) {
++ // Check if there is a factor set on the screen object or associated
++ // with the screen name. These are mutually exclusive, so checking
++ // order is not significant.
++ if (auto qScreen = screen->screen()) {
++ auto screenFactor = qScreen->property(scaleFactorProperty).toReal(&screenPropertyUsed);
++ if (screenPropertyUsed)
++ factor = screenFactor;
++ }
++
++ if (!screenPropertyUsed) {
++ auto byNameIt = QHighDpiScaling::m_namedScreenScaleFactors.constFind(screen->name());
++ if ((screenPropertyUsed = byNameIt != QHighDpiScaling::m_namedScreenScaleFactors.cend()))
++ factor = *byNameIt;
++ }
++ }
++
++ if (!screenPropertyUsed && m_usePlatformPluginDpi)
++ factor = roundScaleFactor(rawScaleFactor(screen));
++
++ return factor;
++ }
++
++ QDpi QHighDpiScaling::logicalDpi(const QScreen *screen)
++ {
++ // (Note: m_active test is performed at call site.)
++ if (!screen || !screen->handle())
++ return QDpi(96, 96);
++
++ if (!m_usePlatformPluginDpi) {
++ const qreal screenScaleFactor = screenSubfactor(screen->handle());
++ const QDpi dpi = QPlatformScreen::overrideDpi(screen->handle()->logicalDpi());
++ return QDpi{ dpi.first / screenScaleFactor, dpi.second / screenScaleFactor };
++ }
++
++ const qreal scaleFactor = rawScaleFactor(screen->handle());
++ const qreal roundedScaleFactor = roundScaleFactor(scaleFactor);
++ return effectiveLogicalDpi(screen->handle(), scaleFactor, roundedScaleFactor);
++ }
++
++ // Returns the screen containing \a position, using \a guess as a starting point
++ // for the search. \a guess might be nullptr. Returns nullptr if \a position is outside
++ // of all screens.
++ QScreen *QHighDpiScaling::screenForPosition(QHighDpiScaling::Point position, QScreen *guess)
++ {
++ if (position.kind == QHighDpiScaling::Point::Invalid)
++ return nullptr;
++
++ auto getPlatformScreenGuess = [](QScreen *maybeScreen) -> QPlatformScreen * {
++ if (maybeScreen)
++ return maybeScreen->handle();
++ if (QScreen *primary = QGuiApplication::primaryScreen())
++ return primary->handle();
++ return nullptr;
++ };
++
++ QPlatformScreen *platformGuess = getPlatformScreenGuess(guess);
++ if (!platformGuess)
++ return nullptr;
++
++ auto onScreen = [](QHighDpiScaling::Point position, const QPlatformScreen *platformScreen) -> bool {
++ return position.kind == Point::Native
++ ? platformScreen->geometry().contains(position.point)
++ : platformScreen->screen()->geometry().contains(position.point);
++ };
++
++ // is the guessed screen correct?
++ if (onScreen(position, platformGuess))
++ return platformGuess->screen();
++
++ // search sibling screens
++ const auto screens = platformGuess->virtualSiblings();
++ for (const QPlatformScreen *screen : screens) {
++ if (onScreen(position, screen))
++ return screen->screen();
++ }
++
++ return nullptr;
++ }
++
++ QList<QHighDpiScaling::ScreenFactor> QHighDpiScaling::parseScreenScaleFactorsSpec(QStringView screenScaleFactors)
++ {
++ QVector<QHighDpiScaling::ScreenFactor> screenFactors;
++
++ // The spec is _either_
++ // - a semicolon-separated ordered factor list: "1.5;2;3"
++ // - a semicolon-separated name=factor list: "foo=1.5;bar=2;baz=3"
++ const auto specs = screenScaleFactors.split(u';');
++ for (const auto &spec : specs) {
++ const qsizetype equalsPos = spec.lastIndexOf(u'=');
++ if (equalsPos == -1) {
++ // screens in order
++ bool ok;
++ const qreal factor = spec.toDouble(&ok);
++ if (ok && factor > 0) {
++ screenFactors.append(QHighDpiScaling::ScreenFactor(QString(), factor));
++ }
++ } else {
++ // "name=factor"
++ bool ok;
++ const qreal factor = spec.mid(equalsPos + 1).toDouble(&ok);
++ if (ok && factor > 0) {
++ screenFactors.append(QHighDpiScaling::ScreenFactor(spec.left(equalsPos).toString(), factor));
++ }
++ }
++ } // for (specs)
++
++ return screenFactors;
++ }
++
++ QHighDpiScaling::ScaleAndOrigin QHighDpiScaling::scaleAndOrigin(const QPlatformScreen *platformScreen, QHighDpiScaling::Point position)
++ {
++ Q_UNUSED(position)
++ if (!m_active)
++ return { qreal(1), QPoint() };
++ if (!platformScreen)
++ return { m_factor, QPoint() }; // the global factor
++ return { m_factor * screenSubfactor(platformScreen), platformScreen->geometry().topLeft() };
++ }
++
++ QHighDpiScaling::ScaleAndOrigin QHighDpiScaling::scaleAndOrigin(const QScreen *screen, QHighDpiScaling::Point position)
++ {
++ Q_UNUSED(position)
++ if (!m_active)
++ return { qreal(1), QPoint() };
++ if (!screen)
++ return { m_factor, QPoint() }; // the global factor
++ return scaleAndOrigin(screen->handle(), position);
++ }
++
++ QHighDpiScaling::ScaleAndOrigin QHighDpiScaling::scaleAndOrigin(const QWindow *window, QHighDpiScaling::Point position)
++ {
++ if (!m_active)
++ return { qreal(1), QPoint() };
++
++ // Determine correct screen; use the screen which contains the given
++ // position if a valid position is passed.
++ QScreen *screen = window ? window->screen() : QGuiApplication::primaryScreen();
++ QScreen *overrideScreen = QHighDpiScaling::screenForPosition(position, screen);
++ QScreen *targetScreen = overrideScreen ? overrideScreen : screen;
++ return scaleAndOrigin(targetScreen, position);
++ }
++
++ #ifndef QT_NO_DEBUG_STREAM
++ QDebug operator<<(QDebug debug, const QHighDpiScaling::ScreenFactor &factor)
++ {
++ const QDebugStateSaver saver(debug);
++ debug.nospace();
++ if (!factor.name.isEmpty())
++ debug << factor.name << "=";
++ debug << factor.factor;
++ return debug;
++ }
++ #endif
++
++ #else // QT_NO_HIGHDPISCALING
++
++ QHighDpiScaling::ScaleAndOrigin QHighDpiScaling::scaleAndOrigin(const QPlatformScreen *, QPoint *)
++ {
++ return { qreal(1), QPoint() };
++ }
++
++ QHighDpiScaling::ScaleAndOrigin QHighDpiScaling::scaleAndOrigin(const QScreen *, QPoint *)
++ {
++ return { qreal(1), QPoint() };
++ }
++
++ QHighDpiScaling::ScaleAndOrigin QHighDpiScaling::scaleAndOrigin(const QWindow *, QPoint *)
++ {
++ return { qreal(1), QPoint() };
++ }
++
++ #endif // QT_NO_HIGHDPISCALING
++
++ QT_END_NAMESPACE
++
++ #include "moc_qhighdpiscaling_p.cpp"