diff options
Diffstat (limited to 'src/gui/kernel/qhighdpiscaling.cpp')
-rw-r--r-- | src/gui/kernel/qhighdpiscaling.cpp | 741 |
1 files changed, 403 insertions, 338 deletions
diff --git a/src/gui/kernel/qhighdpiscaling.cpp b/src/gui/kernel/qhighdpiscaling.cpp index fde6bb0180..a0e1b48dcb 100644 --- a/src/gui/kernel/qhighdpiscaling.cpp +++ b/src/gui/kernel/qhighdpiscaling.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtGui module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// 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 #include "qhighdpiscaling_p.h" #include "qguiapplication.h" @@ -49,17 +13,13 @@ #include <QtCore/qmetaobject.h> #include <algorithm> +#include <optional> QT_BEGIN_NAMESPACE -Q_LOGGING_CATEGORY(lcScaling, "qt.scaling"); +Q_LOGGING_CATEGORY(lcHighDpi, "qt.highdpi"); #ifndef QT_NO_HIGHDPISCALING -static const char legacyDevicePixelEnvVar[] = "QT_DEVICE_PIXEL_RATIO"; - -// Note: QT_AUTO_SCREEN_SCALE_FACTOR is Done on X11, and should be kept -// working as-is. It's Deprecated on all other platforms. -static const char legacyAutoScreenEnvVar[] = "QT_AUTO_SCREEN_SCALE_FACTOR"; static const char enableHighDpiScalingEnvVar[] = "QT_ENABLE_HIGHDPI_SCALING"; static const char scaleFactorEnvVar[] = "QT_SCALE_FACTOR"; @@ -68,47 +28,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<QString, qreal> QScreenScaleFactorHash; -Q_GLOBAL_STATIC(QScreenScaleFactorHash, qNamedScreenScaleFactors); +static std::optional<QString> qEnvironmentVariableOptionalString(const char *name) +{ + if (!qEnvironmentVariableIsSet(name)) + return std::nullopt; + + return std::optional(qEnvironmentVariable(name)); +} + +static std::optional<QByteArray> qEnvironmentVariableOptionalByteArray(const char *name) +{ + if (!qEnvironmentVariableIsSet(name)) + return std::nullopt; + + return std::optional(qgetenv(name)); +} -// 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<int> qEnvironmentVariableOptionalInt(const char *name) { bool ok = false; - int value = qEnvironmentVariableIntValue(name, &ok); - return ok ? value > 0 : defaultValue; + const int value = qEnvironmentVariableIntValue(name, &ok); + auto opt = ok ? std::optional(value) : std::nullopt; + return opt; } -static inline qreal initialGlobalScaleFactor() +static std::optional<qreal> qEnvironmentVariableOptionalReal(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; - } - } else { - // Check for deprecated environment variables. - if (qEnvironmentVariableIsSet(legacyDevicePixelEnvVar)) { - qWarning("Warning: %s is deprecated. Instead use:\n" - " %s to enable platform plugin controlled per-screen factors.\n" - " %s to set per-screen DPI.\n" - " %s to set the application global scale factor.", - legacyDevicePixelEnvVar, legacyAutoScreenEnvVar, screenFactorsEnvVar, scaleFactorEnvVar); - - int dpr = qEnvironmentVariableIntValue(legacyDevicePixelEnvVar); - if (dpr > 0) - result = dpr; - } - } - return result; + bool ok = false; + const qreal value = qEnvironmentVariable(name).toDouble(&ok); + return ok ? std::optional(value) : std::nullopt; } /*! @@ -121,8 +72,9 @@ static inline qreal initialGlobalScaleFactor() \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. In - addition this functionality can be used for simulation and testing purposes. + 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 @@ -152,14 +104,13 @@ static inline qreal initialGlobalScaleFactor() pixels. The devicePixelRatio seen by applications is the product of the Qt scale - factor and the OS scale factor. 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 and Wayland. + 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 functions in this file do not work with the OS scale factor - directly and are limited to converting between device independent and native - pixels. The OS scale factor is accounted for by QWindow::devicePixelRatio() - and similar functions. + 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: @@ -172,7 +123,7 @@ static inline qreal initialGlobalScaleFactor() | Display 100 x 100 | ----------------------------------------------------- - 'Retina Device': Device Independent Pixels = Native Pixels + '2x Apple Device': Device Independent Pixels = Native Pixels --------------------------------------------------- devicePixelRatio: 2 | Application / Qt Gui 100 x 100 | | | Qt Scale Factor: 1 @@ -181,7 +132,7 @@ static inline qreal initialGlobalScaleFactor() | Display 200 x 200 | ----------------------------------------------------- - '2x Qt Scaling': Native Pixels = Device Pixels + 'Windows at 200%': Native Pixels = Device Pixels --------------------------------------------------- devicePixelRatio: 2 | Application / Qt Gui 100 x 100 | |---------------------------------------------------| Qt Scale Factor: 2 @@ -190,112 +141,123 @@ static inline qreal initialGlobalScaleFactor() | Display 200 x 200 | ----------------------------------------------------- - The Qt Scale Factor is the product of two sub-scale factors, 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) A global scale factor - The QT_SCALE_FACTOR environment variable can be used to set - a global scale factor for all windows in the process. This - is useful for testing and debugging (you can simulate any - devicePixelRatio without needing access to special hardware), - and perhaps also for targeting a specific application to - a specific display type (embedded use cases). - - 2) Per-screen scale factors - Some platform plugins support providing a per-screen scale - factor based on display density information. These platforms - include X11, Windows, and Android. - - There are two APIs for enabling or disabling this behavior: - - The QT_AUTO_SCREEN_SCALE_FACTOR environment variable. - - The AA_EnableHighDpiScaling and AA_DisableHighDpiScaling - application attributes - - Enabling either will make QHighDpiScaling call QPlatformScreen::pixelDensity() - and use the value provided as the scale factor for the screen in - question. Disabling is done on a 'veto' basis where either the - environment or the application can disable the scaling. The intended use - cases are 'My system is not providing correct display density - information' and 'My application needs to work in display pixels', - respectively. - - The QT_SCREEN_SCALE_FACTORS environment variable can be used to set the screen - scale factors manually. Set this to a semicolon-separated - list of scale factors (matching the order of QGuiApplications::screens()), - or to a list of name=value pairs (where name matches QScreen::name()). - - Coordinate conversion functions must be used when writing code that passes - geometry across the Qt Gui / Platform plugin boundary. The main conversion - functions are: - T toNativePixels(T, QWindow *) - T fromNativePixels(T, QWindow*) - - The following classes in QtGui use native pixels, for the convenience of the - platform plugins: - QPlatformWindow - QPlatformScreen - QWindowSystemInterface (API only - Events are in device independent pixels) - - As a special consideration platform plugin code should be careful about - calling QtGui geometry accessor functions: - QRect r = window->geometry(); - Here the returned geometry is in device independent pixels. Add a conversion call: - QRect r = QHighDpi::toNativePixels(window->geometry()); - (Avoiding calling QWindow and instead using the QPlatformWindow geometry - might be a better course of action in this case.) + * 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_usePixelDensity = false; // use scale factor from platform plugin -bool QHighDpiScaling::m_pixelDensityScalingActive = false; // pixel density scale factor > 1 +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 - -/* - Initializes the QHighDpiScaling global variables. Called before the - platform plugin is created. -*/ - -static inline bool usePixelDensity() -{ - // Determine if we should set a scale factor based on the pixel density - // reported by the platform plugin. There are several enablers and several - // disablers. A single disable may veto all other enablers. - - // First, check of there is an explicit disable. - if (QCoreApplication::testAttribute(Qt::AA_DisableHighDpiScaling)) - return false; - bool screenEnvValueOk; - const int screenEnvValue = qEnvironmentVariableIntValue(legacyAutoScreenEnvVar, &screenEnvValueOk); - if (screenEnvValueOk && screenEnvValue < 1) - return false; - bool enableEnvValueOk; - const int enableEnvValue = qEnvironmentVariableIntValue(enableHighDpiScalingEnvVar, &enableEnvValueOk); - if (enableEnvValueOk && enableEnvValue < 1) - return false; - - // Then return if there was an enable. - return QCoreApplication::testAttribute(Qt::AA_EnableHighDpiScaling) - || (screenEnvValueOk && screenEnvValue > 0) - || (enableEnvValueOk && enableEnvValue > 0) - || (qEnvironmentVariableIsSet(legacyDevicePixelEnvVar) - && qEnvironmentVariable(legacyDevicePixelEnvVar).compare(QLatin1String("auto"), Qt::CaseInsensitive) == 0); -} +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) { - // 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); + 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); @@ -375,32 +337,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; @@ -424,11 +363,9 @@ qreal QHighDpiScaling::roundScaleFactor(qreal rawFactor) 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 != Qt::HighDpiScaleFactorRoundingPolicy::PassThrough) - roundedFactor = qMax(roundedFactor, qreal(1)); + // Clamp the minimum factor to 1. Qt does not currently render + // correctly with factors less than 1. + roundedFactor = qMax(roundedFactor, qreal(1)); return roundedFactor; } @@ -442,100 +379,154 @@ 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(); + qCDebug(lcHighDpi) << "Initializing high-DPI scaling"; + + // Read environment variables + static const char* envDebugStr = "environment variable set:"; + std::optional<int> envEnableHighDpiScaling = qEnvironmentVariableOptionalInt(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(); + + std::optional<QString> envScreenFactors = qEnvironmentVariableOptionalString(screenFactorsEnvVar); + if (envScreenFactors.has_value()) + qCDebug(lcHighDpi) << envDebugStr << screenFactorsEnvVar << envScreenFactors.value(); + + std::optional<int> envUsePhysicalDpi = qEnvironmentVariableOptionalInt(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)); - m_usePixelDensity = usePixelDensity(); + // 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. + QString screenFactorsSpec = envScreenFactors.value_or(QString()); + m_screenFactors = parseScreenScaleFactorsSpec(QStringView{screenFactorsSpec}); + 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) { + 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 = 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()); + } + } - m_pixelDensityScalingActive = false; //set in updateHighDpiScaling below + // Set initial active state + m_active = m_globalScalingActive || m_usePlatformPluginDpi; - m_active = m_globalScalingActive || m_usePixelDensity; + 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() { - if (QCoreApplication::testAttribute(Qt::AA_DisableHighDpiScaling)) - return; + qCDebug(lcHighDpi) << "Updating high-DPI scaling"; - if (m_usePixelDensity && !m_pixelDensityScalingActive) { + // 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 (QScreen *screen : screens) { - if (!qFuzzyCompare(screenSubfactor(screen->handle()), qreal(1))) { - m_pixelDensityScalingActive = true; - break; + 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; + } + } } } } - if (qEnvironmentVariableIsSet(screenFactorsEnvVar)) { - int i = 0; - const QString spec = qEnvironmentVariable(screenFactorsEnvVar); - const auto specs = spec.splitRef(QLatin1Char(';')); - for (const QStringRef &spec : specs) { - int equalsPos = spec.lastIndexOf(QLatin1Char('=')); - qreal factor = 0; - if (equalsPos > 0) { - // support "name=factor" - bool ok; - const auto name = spec.left(equalsPos); - factor = spec.mid(equalsPos + 1).toDouble(&ok); - if (ok && factor > 0 ) { - const auto screens = QGuiApplication::screens(); - for (QScreen *s : screens) { - if (s->name() == name) { - setScreenFactor(s, factor); - break; - } - } - } - } else { - // listing screens in order - bool ok; - factor = spec.toDouble(&ok); - if (ok && factor > 0 && i < QGuiApplication::screens().count()) { - QScreen *screen = QGuiApplication::screens().at(i); - setScreenFactor(screen, factor); - } + + // 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; } - ++i; } } - m_active = m_globalScalingActive || m_screenFactorSet || m_usePixelDensity; + + m_active = m_globalScalingActive || m_screenFactorSet || m_platformPluginDpiScalingActive; + + qCDebug(lcHighDpi) << "Update done, high-DPI scaling is" + << (m_active ? "active" : "inactive"); } /* @@ -543,17 +534,24 @@ void QHighDpiScaling::updateHighDpiScaling() */ 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_pixelDensityScalingActive; - const auto screens = QGuiApplication::screens(); + m_active = m_globalScalingActive || m_screenFactorSet || m_platformPluginDpiScalingActive ; for (QScreen *screen : screens) - screen->d_func()->updateHighDpi(); + screen->d_func()->updateGeometry(); } static const char scaleFactorProperty[] = "_q_scaleFactor"; @@ -563,22 +561,24 @@ static const char scaleFactorProperty[] = "_q_scaleFactor"; */ 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 - qNamedScreenScaleFactors()->insert(name, factor); + QHighDpiScaling::m_namedScreenScaleFactors.insert(name, factor); - // hack to force re-evaluation of screen geometry - if (screen->handle()) - screen->d_func()->setPlatformScreen(screen->handle()); // updates geometries based on scale factor + screen->d_func()->updateGeometry(); } QPoint QHighDpiScaling::mapPositionToNative(const QPoint &pos, const QPlatformScreen *platformScreen) @@ -599,46 +599,6 @@ QPoint QHighDpiScaling::mapPositionFromNative(const QPoint &pos, const QPlatform return (pos - topLeft) / scaleFactor + topLeft; } -QPoint QHighDpiScaling::mapPositionToGlobal(const QPoint &pos, const QPoint &windowGlobalPosition, const QWindow *window) -{ - QPoint globalPosCandidate = pos + windowGlobalPosition; - if (QGuiApplicationPrivate::screen_list.size() <= 1) - return globalPosCandidate; - - // The global position may be outside device independent screen geometry - // in cases where a window spans screens. Detect this case and map via - // native coordinates to the correct screen. - auto currentScreen = window->screen(); - if (currentScreen && !currentScreen->geometry().contains(globalPosCandidate)) { - auto nativeGlobalPos = QHighDpi::toNativePixels(globalPosCandidate, currentScreen); - if (auto actualPlatformScreen = currentScreen->handle()->screenForPosition(nativeGlobalPos)) - return QHighDpi::fromNativePixels(nativeGlobalPos, actualPlatformScreen->screen()); - } - - return globalPosCandidate; -} - -QPoint QHighDpiScaling::mapPositionFromGlobal(const QPoint &pos, const QPoint &windowGlobalPosition, const QWindow *window) -{ - QPoint windowPosCandidate = pos - windowGlobalPosition; - if (QGuiApplicationPrivate::screen_list.size() <= 1 || window->handle() == nullptr) - return windowPosCandidate; - - // Device independent global (screen) space may discontiguous when high-dpi scaling - // is active. This means that the normal subtracting of the window global position from the - // position-to-be-mapped may not work in cases where a window spans multiple screens. - // Map both positions to native global space (using the correct screens), subtract there, - // and then map the difference back using the scale factor for the window. - QScreen *posScreen = QGuiApplication::screenAt(pos); - if (posScreen && posScreen != window->screen()) { - QPoint nativePos = QHighDpi::toNativePixels(pos, posScreen); - QPoint windowNativePos = window->handle()->geometry().topLeft(); - return QHighDpi::fromNativeLocalPosition(nativePos - windowNativePos, window); - } - - return windowPosCandidate; -} - qreal QHighDpiScaling::screenSubfactor(const QPlatformScreen *screen) { auto factor = qreal(1.0); @@ -661,13 +621,13 @@ 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; } } - if (!screenPropertyUsed && m_usePixelDensity) + if (!screenPropertyUsed && m_usePlatformPluginDpi) factor = roundScaleFactor(rawScaleFactor(screen)); return factor; @@ -679,7 +639,7 @@ QDpi QHighDpiScaling::logicalDpi(const QScreen *screen) if (!screen || !screen->handle()) return QDpi(96, 96); - if (!m_usePixelDensity) { + 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 }; @@ -690,35 +650,140 @@ QDpi QHighDpiScaling::logicalDpi(const QScreen *screen) return effectiveLogicalDpi(screen->handle(), scaleFactor, roundedScaleFactor); } -QHighDpiScaling::ScaleAndOrigin QHighDpiScaling::scaleAndOrigin(const QPlatformScreen *platformScreen, QPoint *nativePosition) +// 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; +} + +QVector<QHighDpiScaling::ScreenFactor> QHighDpiScaling::parseScreenScaleFactorsSpec(const 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 - const QPlatformScreen *actualScreen = nativePosition ? - platformScreen->screenForPosition(*nativePosition) : platformScreen; - return { m_factor * screenSubfactor(actualScreen), actualScreen->geometry().topLeft() }; + return { m_factor * screenSubfactor(platformScreen), platformScreen->geometry().topLeft() }; } -QHighDpiScaling::ScaleAndOrigin QHighDpiScaling::scaleAndOrigin(const QScreen *screen, QPoint *nativePosition) +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(), nativePosition); + return scaleAndOrigin(screen->handle(), position); } -QHighDpiScaling::ScaleAndOrigin QHighDpiScaling::scaleAndOrigin(const QWindow *window, QPoint *nativePosition) +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(); - const bool searchScreen = !window || window->isTopLevel(); - return scaleAndOrigin(screen, searchScreen ? nativePosition : nullptr); + 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() }; } -#endif //QT_NO_HIGHDPISCALING +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" |