1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtGui module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qhighdpiscaling_p.h"
41 #include "qguiapplication.h"
42 #include "qscreen.h"
43 #include "qplatformintegration.h"
44 #include "qplatformwindow.h"
45 #include "private/qscreen_p.h"
46 #include <private/qguiapplication_p.h>
47 
48 #include <QtCore/qdebug.h>
49 #include <QtCore/qmetaobject.h>
50 
51 #include <algorithm>
52 
53 QT_BEGIN_NAMESPACE
54 
55 Q_LOGGING_CATEGORY(lcScaling, "qt.scaling");
56 
57 #ifndef QT_NO_HIGHDPISCALING
58 static const char legacyDevicePixelEnvVar[] = "QT_DEVICE_PIXEL_RATIO";
59 
60 // Note: QT_AUTO_SCREEN_SCALE_FACTOR is Done on X11, and should be kept
61 // working as-is. It's Deprecated on all other platforms.
62 static const char legacyAutoScreenEnvVar[] = "QT_AUTO_SCREEN_SCALE_FACTOR";
63 
64 static const char enableHighDpiScalingEnvVar[] = "QT_ENABLE_HIGHDPI_SCALING";
65 static const char scaleFactorEnvVar[] = "QT_SCALE_FACTOR";
66 static const char screenFactorsEnvVar[] = "QT_SCREEN_SCALE_FACTORS";
67 static const char scaleFactorRoundingPolicyEnvVar[] = "QT_SCALE_FACTOR_ROUNDING_POLICY";
68 static const char dpiAdjustmentPolicyEnvVar[] = "QT_DPI_ADJUSTMENT_POLICY";
69 static const char usePhysicalDpiEnvVar[] = "QT_USE_PHYSICAL_DPI";
70 
71 // Per-screen scale factors for named screens set with QT_SCREEN_SCALE_FACTORS
72 // are stored here. Use a global hash to keep the factor across screen
73 // disconnect/connect cycles where the screen object may be deleted.
74 typedef QHash<QString, qreal> QScreenScaleFactorHash;
75 Q_GLOBAL_STATIC(QScreenScaleFactorHash, qNamedScreenScaleFactors);
76 
77 // Reads and interprets the given environment variable as a bool,
78 // returns the default value if not set.
qEnvironmentVariableAsBool(const char * name,bool defaultValue)79 static bool qEnvironmentVariableAsBool(const char *name, bool defaultValue)
80 {
81     bool ok = false;
82     int value = qEnvironmentVariableIntValue(name, &ok);
83     return ok ? value > 0 : defaultValue;
84 }
85 
initialGlobalScaleFactor()86 static inline qreal initialGlobalScaleFactor()
87 {
88 
89     qreal result = 1;
90     if (qEnvironmentVariableIsSet(scaleFactorEnvVar)) {
91         bool ok;
92         const qreal f = qEnvironmentVariable(scaleFactorEnvVar).toDouble(&ok);
93         if (ok && f > 0) {
94             qCDebug(lcScaling) << "Apply " << scaleFactorEnvVar << f;
95             result = f;
96         }
97     } else {
98         // Check for deprecated environment variables.
99         if (qEnvironmentVariableIsSet(legacyDevicePixelEnvVar)) {
100             qWarning("Warning: %s is deprecated. Instead use:\n"
101                      "   %s to enable platform plugin controlled per-screen factors.\n"
102                      "   %s to set per-screen DPI.\n"
103                      "   %s to set the application global scale factor.",
104                      legacyDevicePixelEnvVar, legacyAutoScreenEnvVar, screenFactorsEnvVar, scaleFactorEnvVar);
105 
106             int dpr = qEnvironmentVariableIntValue(legacyDevicePixelEnvVar);
107             if (dpr > 0)
108                 result = dpr;
109         }
110     }
111     return result;
112 }
113 
114 /*!
115     \class QHighDpiScaling
116     \since 5.6
117     \internal
118     \preliminary
119     \ingroup qpa
120 
121     \brief Collection of utility functions for UI scaling.
122 
123     QHighDpiScaling implements utility functions for high-dpi scaling for use
124     on operating systems that provide limited support for native scaling. In
125     addition this functionality can be used for simulation and testing purposes.
126 
127     The functions support scaling between the device independent coordinate
128     system used by Qt applications and the native coordinate system used by
129     the platform plugins. Intended usage locations are the low level / platform
130     plugin interfacing parts of QtGui, for example the QWindow, QScreen and
131     QWindowSystemInterface implementation.
132 
133     There are now up to three active coordinate systems in Qt:
134 
135      ---------------------------------------------------
136     |  Application            Device Independent Pixels |   devicePixelRatio
137     |  Qt Widgets                                       |         =
138     |  Qt Gui                                           |
139     |---------------------------------------------------|   Qt Scale Factor
140     |  Qt Gui QPlatform*      Native Pixels             |         *
141     |  Qt platform plugin                               |
142     |---------------------------------------------------|   OS Scale Factor
143     |  Display                Device Pixels             |
144     |  (Graphics Buffers)                               |
145     -----------------------------------------------------
146 
147     This is an simplification and shows the main coordinate system. All layers
148     may work with device pixels in specific cases: OpenGL, creating the backing
149     store, and QPixmap management. The "Native Pixels" coordinate system is
150     internal to Qt and should not be exposed to Qt users: Seen from the outside
151     there are only two coordinate systems: device independent pixels and device
152     pixels.
153 
154     The devicePixelRatio seen by applications is the product of the Qt scale
155     factor and the OS scale factor. The value of the scale factors may be 1,
156     in which case two or more of the coordinate systems are equivalent. Platforms
157     that (may) have an OS scale factor include \macos, iOS and Wayland.
158 
159     Note that the functions in this file do not work with the OS scale factor
160     directly and are limited to converting between device independent and native
161     pixels. The OS scale factor is accounted for by QWindow::devicePixelRatio()
162     and similar functions.
163 
164     Configuration Examples:
165 
166     'Classic': Device Independent Pixels = Native Pixels = Device Pixels
167      ---------------------------------------------------    devicePixelRatio: 1
168     |  Application / Qt Gui             100 x 100       |
169     |                                                   |   Qt Scale Factor: 1
170     |  Qt Platform / OS                 100 x 100       |
171     |                                                   |   OS Scale Factor: 1
172     |  Display                          100 x 100       |
173     -----------------------------------------------------
174 
175     'Retina Device': Device Independent Pixels = Native Pixels
176      ---------------------------------------------------    devicePixelRatio: 2
177     |  Application / Qt Gui             100 x 100       |
178     |                                                   |   Qt Scale Factor: 1
179     |  Qt Platform / OS                 100 x 100       |
180     |---------------------------------------------------|   OS Scale Factor: 2
181     |  Display                          200 x 200       |
182     -----------------------------------------------------
183 
184     '2x Qt Scaling': Native Pixels = Device Pixels
185      ---------------------------------------------------    devicePixelRatio: 2
186     |  Application / Qt Gui             100 x 100       |
187     |---------------------------------------------------|   Qt Scale Factor: 2
188     |  Qt Platform / OS                 200 x 200       |
189     |                                                   |   OS Scale Factor: 1
190     |  Display                          200 x 200       |
191     -----------------------------------------------------
192 
193     The Qt Scale Factor is the product of two sub-scale factors, which
194     are independently either set or determined by the platform plugin.
195     Several APIs are offered for this, targeting both developers and
196     end users. All scale factors are of type qreal.
197 
198     1) A global scale factor
199         The QT_SCALE_FACTOR environment variable can be used to set
200         a global scale factor for all windows in the process. This
201         is useful for testing and debugging (you can simulate any
202         devicePixelRatio without needing access to special hardware),
203         and perhaps also for targeting a specific application to
204         a specific display type (embedded use cases).
205 
206     2) Per-screen scale factors
207         Some platform plugins support providing a per-screen scale
208         factor based on display density information. These platforms
209         include X11, Windows, and Android.
210 
211         There are two APIs for enabling or disabling this behavior:
212             - The QT_AUTO_SCREEN_SCALE_FACTOR environment variable.
213             - The AA_EnableHighDpiScaling and AA_DisableHighDpiScaling
214               application attributes
215 
216         Enabling either will make QHighDpiScaling call QPlatformScreen::pixelDensity()
217         and use the value provided as the scale factor for the screen in
218         question. Disabling is done on a 'veto' basis where either the
219         environment or the application can disable the scaling. The intended use
220         cases are 'My system is not providing correct display density
221         information' and 'My application needs to work in display pixels',
222         respectively.
223 
224         The QT_SCREEN_SCALE_FACTORS environment variable can be used to set the screen
225         scale factors manually. Set this to a semicolon-separated
226         list of scale factors (matching the order of QGuiApplications::screens()),
227         or to a list of name=value pairs (where name matches QScreen::name()).
228 
229     Coordinate conversion functions must be used when writing code that passes
230     geometry across the Qt Gui / Platform plugin boundary. The main conversion
231     functions are:
232         T toNativePixels(T, QWindow *)
233         T fromNativePixels(T, QWindow*)
234 
235     The following classes in QtGui use native pixels, for the convenience of the
236     platform plugins:
237         QPlatformWindow
238         QPlatformScreen
239         QWindowSystemInterface (API only - Events are in device independent pixels)
240 
241     As a special consideration platform plugin code should be careful about
242     calling QtGui geometry accessor functions:
243         QRect r = window->geometry();
244     Here the returned geometry is in device independent pixels. Add a conversion call:
245         QRect r = QHighDpi::toNativePixels(window->geometry());
246     (Avoiding calling QWindow and instead using the QPlatformWindow geometry
247      might be a better course of action in this case.)
248 */
249 
250 qreal QHighDpiScaling::m_factor = 1.0;
251 bool QHighDpiScaling::m_active = false; //"overall active" - is there any scale factor set.
252 bool QHighDpiScaling::m_usePixelDensity = false; // use scale factor from platform plugin
253 bool QHighDpiScaling::m_pixelDensityScalingActive = false; // pixel density scale factor > 1
254 bool QHighDpiScaling::m_globalScalingActive = false; // global scale factor is active
255 bool QHighDpiScaling::m_screenFactorSet = false; // QHighDpiScaling::setScreenFactor has been used
256 
257 /*
258     Initializes the QHighDpiScaling global variables. Called before the
259     platform plugin is created.
260 */
261 
usePixelDensity()262 static inline bool usePixelDensity()
263 {
264     // Determine if we should set a scale factor based on the pixel density
265     // reported by the platform plugin. There are several enablers and several
266     // disablers. A single disable may veto all other enablers.
267 
268     // First, check of there is an explicit disable.
269     if (QCoreApplication::testAttribute(Qt::AA_DisableHighDpiScaling))
270         return false;
271     bool screenEnvValueOk;
272     const int screenEnvValue = qEnvironmentVariableIntValue(legacyAutoScreenEnvVar, &screenEnvValueOk);
273     if (screenEnvValueOk && screenEnvValue < 1)
274         return false;
275     bool enableEnvValueOk;
276     const int enableEnvValue = qEnvironmentVariableIntValue(enableHighDpiScalingEnvVar, &enableEnvValueOk);
277     if (enableEnvValueOk && enableEnvValue < 1)
278         return false;
279 
280     // Then return if there was an enable.
281     return QCoreApplication::testAttribute(Qt::AA_EnableHighDpiScaling)
282         || (screenEnvValueOk && screenEnvValue > 0)
283         || (enableEnvValueOk && enableEnvValue > 0)
284         || (qEnvironmentVariableIsSet(legacyDevicePixelEnvVar)
285             && qEnvironmentVariable(legacyDevicePixelEnvVar).compare(QLatin1String("auto"), Qt::CaseInsensitive) == 0);
286 }
287 
rawScaleFactor(const QPlatformScreen * screen)288 qreal QHighDpiScaling::rawScaleFactor(const QPlatformScreen *screen)
289 {
290     // Determine if physical DPI should be used
291     static const bool usePhysicalDpi = qEnvironmentVariableAsBool(usePhysicalDpiEnvVar, false);
292 
293     // Calculate scale factor beased on platform screen DPI values
294     qreal factor;
295     QDpi platformBaseDpi = screen->logicalBaseDpi();
296     if (usePhysicalDpi) {
297         QSize sz = screen->geometry().size();
298         QSizeF psz = screen->physicalSize();
299         qreal platformPhysicalDpi = ((sz.height() / psz.height()) + (sz.width() / psz.width())) * qreal(25.4 * 0.5);
300         factor = qreal(platformPhysicalDpi) / qreal(platformBaseDpi.first);
301     } else {
302         const QDpi platformLogicalDpi = QPlatformScreen::overrideDpi(screen->logicalDpi());
303         factor = qreal(platformLogicalDpi.first) / qreal(platformBaseDpi.first);
304     }
305 
306     return factor;
307 }
308 
309 template <class EnumType>
310 struct EnumLookup
311 {
312     const char *name;
313     EnumType value;
314 };
315 
316 template <class EnumType>
operator ==(const EnumLookup<EnumType> & e1,const EnumLookup<EnumType> & e2)317 static bool operator==(const EnumLookup<EnumType> &e1, const EnumLookup<EnumType> &e2)
318 {
319     return qstricmp(e1.name, e2.name) == 0;
320 }
321 
322 template <class EnumType>
joinEnumValues(const EnumLookup<EnumType> * i1,const EnumLookup<EnumType> * i2)323 static QByteArray joinEnumValues(const EnumLookup<EnumType> *i1, const EnumLookup<EnumType> *i2)
324 {
325     QByteArray result;
326     for (; i1 < i2; ++i1) {
327         if (!result.isEmpty())
328             result += QByteArrayLiteral(", ");
329         result += i1->name;
330     }
331     return result;
332 }
333 
334 using ScaleFactorRoundingPolicyLookup = EnumLookup<Qt::HighDpiScaleFactorRoundingPolicy>;
335 
336 static const ScaleFactorRoundingPolicyLookup scaleFactorRoundingPolicyLookup[] =
337 {
338     {"Round", Qt::HighDpiScaleFactorRoundingPolicy::Round},
339     {"Ceil", Qt::HighDpiScaleFactorRoundingPolicy::Ceil},
340     {"Floor", Qt::HighDpiScaleFactorRoundingPolicy::Floor},
341     {"RoundPreferFloor", Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor},
342     {"PassThrough", Qt::HighDpiScaleFactorRoundingPolicy::PassThrough}
343 };
344 
345 static Qt::HighDpiScaleFactorRoundingPolicy
lookupScaleFactorRoundingPolicy(const QByteArray & v)346     lookupScaleFactorRoundingPolicy(const QByteArray &v)
347 {
348     auto end = std::end(scaleFactorRoundingPolicyLookup);
349     auto it = std::find(std::begin(scaleFactorRoundingPolicyLookup), end,
350                         ScaleFactorRoundingPolicyLookup{v.constData(), Qt::HighDpiScaleFactorRoundingPolicy::Unset});
351     return it != end ? it->value : Qt::HighDpiScaleFactorRoundingPolicy::Unset;
352 }
353 
354 using DpiAdjustmentPolicyLookup = EnumLookup<QHighDpiScaling::DpiAdjustmentPolicy>;
355 
356 static const DpiAdjustmentPolicyLookup dpiAdjustmentPolicyLookup[] =
357 {
358     {"AdjustDpi", QHighDpiScaling::DpiAdjustmentPolicy::Enabled},
359     {"DontAdjustDpi", QHighDpiScaling::DpiAdjustmentPolicy::Disabled},
360     {"AdjustUpOnly", QHighDpiScaling::DpiAdjustmentPolicy::UpOnly}
361 };
362 
363 static QHighDpiScaling::DpiAdjustmentPolicy
lookupDpiAdjustmentPolicy(const QByteArray & v)364     lookupDpiAdjustmentPolicy(const QByteArray &v)
365 {
366     auto end = std::end(dpiAdjustmentPolicyLookup);
367     auto it = std::find(std::begin(dpiAdjustmentPolicyLookup), end,
368                         DpiAdjustmentPolicyLookup{v.constData(), QHighDpiScaling::DpiAdjustmentPolicy::Unset});
369     return it != end ? it->value : QHighDpiScaling::DpiAdjustmentPolicy::Unset;
370 }
371 
roundScaleFactor(qreal rawFactor)372 qreal QHighDpiScaling::roundScaleFactor(qreal rawFactor)
373 {
374     // Apply scale factor rounding policy. Using mathematically correct rounding
375     // may not give the most desirable visual results, especially for
376     // critical fractions like .5. In general, rounding down results in visual
377     // sizes that are smaller than the ideal size, and opposite for rounding up.
378     // Rounding down is then preferable since "small UI" is a more acceptable
379     // high-DPI experience than "large UI".
380     static auto scaleFactorRoundingPolicy = Qt::HighDpiScaleFactorRoundingPolicy::Unset;
381 
382     // Determine rounding policy
383     if (scaleFactorRoundingPolicy == Qt::HighDpiScaleFactorRoundingPolicy::Unset) {
384         // Check environment
385         if (qEnvironmentVariableIsSet(scaleFactorRoundingPolicyEnvVar)) {
386             QByteArray policyText = qgetenv(scaleFactorRoundingPolicyEnvVar);
387             auto policyEnumValue = lookupScaleFactorRoundingPolicy(policyText);
388             if (policyEnumValue != Qt::HighDpiScaleFactorRoundingPolicy::Unset) {
389                 scaleFactorRoundingPolicy = policyEnumValue;
390             } else {
391                 auto values = joinEnumValues(std::begin(scaleFactorRoundingPolicyLookup),
392                                              std::end(scaleFactorRoundingPolicyLookup));
393                 qWarning("Unknown scale factor rounding policy: %s. Supported values are: %s.",
394                          policyText.constData(), values.constData());
395             }
396         }
397 
398         // Check application object if no environment value was set.
399         if (scaleFactorRoundingPolicy == Qt::HighDpiScaleFactorRoundingPolicy::Unset) {
400             scaleFactorRoundingPolicy = QGuiApplication::highDpiScaleFactorRoundingPolicy();
401         } else {
402             // Make application setting reflect environment
403             QGuiApplication::setHighDpiScaleFactorRoundingPolicy(scaleFactorRoundingPolicy);
404         }
405     }
406 
407     // Apply rounding policy.
408     qreal roundedFactor = rawFactor;
409     switch (scaleFactorRoundingPolicy) {
410     case Qt::HighDpiScaleFactorRoundingPolicy::Round:
411         roundedFactor = qRound(rawFactor);
412         break;
413     case Qt::HighDpiScaleFactorRoundingPolicy::Ceil:
414         roundedFactor = qCeil(rawFactor);
415         break;
416     case Qt::HighDpiScaleFactorRoundingPolicy::Floor:
417         roundedFactor = qFloor(rawFactor);
418         break;
419     case Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor:
420         // Round up for .75 and higher. This favors "small UI" over "large UI".
421         roundedFactor = rawFactor - qFloor(rawFactor) < 0.75
422             ? qFloor(rawFactor) : qCeil(rawFactor);
423         break;
424     case Qt::HighDpiScaleFactorRoundingPolicy::PassThrough:
425     case Qt::HighDpiScaleFactorRoundingPolicy::Unset:
426         break;
427     }
428 
429     // Don't round down to to zero; clamp the minimum (rounded) factor to 1.
430     // This is not a common case but can happen if a display reports a very
431     // low DPI.
432     if (scaleFactorRoundingPolicy != Qt::HighDpiScaleFactorRoundingPolicy::PassThrough)
433         roundedFactor = qMax(roundedFactor, qreal(1));
434 
435     return roundedFactor;
436 }
437 
effectiveLogicalDpi(const QPlatformScreen * screen,qreal rawFactor,qreal roundedFactor)438 QDpi QHighDpiScaling::effectiveLogicalDpi(const QPlatformScreen *screen, qreal rawFactor, qreal roundedFactor)
439 {
440     // Apply DPI adjustment policy, if needed. If enabled this will change the
441     // reported logical DPI to account for the difference between the rounded
442     // scale factor and the actual scale factor. The effect is that text size
443     // will be correct for the screen dpi, but may be (slightly) out of sync
444     // with the rest of the UI. The amount of out-of-synch-ness depends on how
445     // well user code handles a non-standard DPI values, but since the
446     // adjustment is small (typically +/- 48 max) this might be OK.
447     static auto dpiAdjustmentPolicy = DpiAdjustmentPolicy::Unset;
448 
449     // Determine adjustment policy.
450     if (dpiAdjustmentPolicy == DpiAdjustmentPolicy::Unset) {
451         if (qEnvironmentVariableIsSet(dpiAdjustmentPolicyEnvVar)) {
452             QByteArray policyText = qgetenv(dpiAdjustmentPolicyEnvVar);
453             auto policyEnumValue = lookupDpiAdjustmentPolicy(policyText);
454             if (policyEnumValue != DpiAdjustmentPolicy::Unset) {
455                 dpiAdjustmentPolicy = policyEnumValue;
456             } else {
457                 auto values = joinEnumValues(std::begin(dpiAdjustmentPolicyLookup),
458                                              std::end(dpiAdjustmentPolicyLookup));
459                 qWarning("Unknown DPI adjustment policy: %s. Supported values are: %s.",
460                          policyText.constData(), values.constData());
461             }
462         }
463         if (dpiAdjustmentPolicy == DpiAdjustmentPolicy::Unset)
464             dpiAdjustmentPolicy = DpiAdjustmentPolicy::UpOnly;
465     }
466 
467     // Apply adjustment policy.
468     const QDpi baseDpi = screen->logicalBaseDpi();
469     const qreal dpiAdjustmentFactor = rawFactor / roundedFactor;
470 
471     // Return the base DPI for cases where there is no adjustment
472     if (dpiAdjustmentPolicy == DpiAdjustmentPolicy::Disabled)
473         return baseDpi;
474     if (dpiAdjustmentPolicy == DpiAdjustmentPolicy::UpOnly && dpiAdjustmentFactor < 1)
475         return baseDpi;
476 
477     return QDpi(baseDpi.first * dpiAdjustmentFactor, baseDpi.second * dpiAdjustmentFactor);
478 }
479 
initHighDpiScaling()480 void QHighDpiScaling::initHighDpiScaling()
481 {
482     // Determine if there is a global scale factor set.
483     m_factor = initialGlobalScaleFactor();
484     m_globalScalingActive = !qFuzzyCompare(m_factor, qreal(1));
485 
486     m_usePixelDensity = usePixelDensity();
487 
488     m_pixelDensityScalingActive = false; //set in updateHighDpiScaling below
489 
490     m_active = m_globalScalingActive || m_usePixelDensity;
491 }
492 
updateHighDpiScaling()493 void QHighDpiScaling::updateHighDpiScaling()
494 {
495     if (QCoreApplication::testAttribute(Qt::AA_DisableHighDpiScaling))
496         return;
497 
498     m_usePixelDensity = usePixelDensity();
499 
500     if (m_usePixelDensity && !m_pixelDensityScalingActive) {
501         const auto screens = QGuiApplication::screens();
502         for (QScreen *screen : screens) {
503             if (!qFuzzyCompare(screenSubfactor(screen->handle()), qreal(1))) {
504                 m_pixelDensityScalingActive = true;
505                 break;
506             }
507         }
508     }
509     if (qEnvironmentVariableIsSet(screenFactorsEnvVar)) {
510         int i = 0;
511         const QString spec = qEnvironmentVariable(screenFactorsEnvVar);
512         const auto specs = spec.splitRef(QLatin1Char(';'));
513         for (const QStringRef &spec : specs) {
514             int equalsPos = spec.lastIndexOf(QLatin1Char('='));
515             qreal factor = 0;
516             if (equalsPos > 0) {
517                 // support "name=factor"
518                 bool ok;
519                 const auto name = spec.left(equalsPos);
520                 factor = spec.mid(equalsPos + 1).toDouble(&ok);
521                 if (ok && factor > 0 ) {
522                     const auto screens = QGuiApplication::screens();
523                     for (QScreen *s : screens) {
524                         if (s->name() == name) {
525                             setScreenFactor(s, factor);
526                             break;
527                         }
528                     }
529                 }
530             } else {
531                 // listing screens in order
532                 bool ok;
533                 factor = spec.toDouble(&ok);
534                 if (ok && factor > 0 && i < QGuiApplication::screens().count()) {
535                     QScreen *screen = QGuiApplication::screens().at(i);
536                     setScreenFactor(screen, factor);
537                 }
538             }
539             ++i;
540         }
541     }
542     m_active = m_globalScalingActive || m_screenFactorSet || m_pixelDensityScalingActive;
543 }
544 
545 /*
546     Sets the global scale factor which is applied to all windows.
547 */
setGlobalFactor(qreal factor)548 void QHighDpiScaling::setGlobalFactor(qreal factor)
549 {
550     if (qFuzzyCompare(factor, m_factor))
551         return;
552     if (!QGuiApplication::allWindows().isEmpty())
553         qWarning("QHighDpiScaling::setFactor: Should only be called when no windows exist.");
554 
555     m_globalScalingActive = !qFuzzyCompare(factor, qreal(1));
556     m_factor = m_globalScalingActive ? factor : qreal(1);
557     m_active = m_globalScalingActive || m_screenFactorSet || m_pixelDensityScalingActive;
558     const auto screens = QGuiApplication::screens();
559     for (QScreen *screen : screens)
560          screen->d_func()->updateHighDpi();
561 }
562 
563 static const char scaleFactorProperty[] = "_q_scaleFactor";
564 
565 /*
566     Sets a per-screen scale factor.
567 */
setScreenFactor(QScreen * screen,qreal factor)568 void QHighDpiScaling::setScreenFactor(QScreen *screen, qreal factor)
569 {
570     if (!qFuzzyCompare(factor, qreal(1))) {
571         m_screenFactorSet = true;
572         m_active = true;
573     }
574 
575     // Prefer associating the factor with screen name over the object
576     // since the screen object may be deleted on screen disconnects.
577     const QString name = screen->name();
578     if (name.isEmpty())
579         screen->setProperty(scaleFactorProperty, QVariant(factor));
580     else
581         qNamedScreenScaleFactors()->insert(name, factor);
582 
583     // hack to force re-evaluation of screen geometry
584     if (screen->handle())
585         screen->d_func()->setPlatformScreen(screen->handle()); // updates geometries based on scale factor
586 }
587 
mapPositionToNative(const QPoint & pos,const QPlatformScreen * platformScreen)588 QPoint QHighDpiScaling::mapPositionToNative(const QPoint &pos, const QPlatformScreen *platformScreen)
589 {
590     if (!platformScreen)
591         return pos;
592     const qreal scaleFactor = factor(platformScreen);
593     const QPoint topLeft = platformScreen->geometry().topLeft();
594     return (pos - topLeft) * scaleFactor + topLeft;
595 }
596 
mapPositionFromNative(const QPoint & pos,const QPlatformScreen * platformScreen)597 QPoint QHighDpiScaling::mapPositionFromNative(const QPoint &pos, const QPlatformScreen *platformScreen)
598 {
599     if (!platformScreen)
600         return pos;
601     const qreal scaleFactor = factor(platformScreen);
602     const QPoint topLeft = platformScreen->geometry().topLeft();
603     return (pos - topLeft) / scaleFactor + topLeft;
604 }
605 
mapPositionToGlobal(const QPoint & pos,const QPoint & windowGlobalPosition,const QWindow * window)606 QPoint QHighDpiScaling::mapPositionToGlobal(const QPoint &pos, const QPoint &windowGlobalPosition, const QWindow *window)
607 {
608     QPoint globalPosCandidate = pos + windowGlobalPosition;
609     if (QGuiApplicationPrivate::screen_list.size() <= 1)
610         return globalPosCandidate;
611 
612     // The global position may be outside device independent screen geometry
613     // in cases where a window spans screens. Detect this case and map via
614     // native coordinates to the correct screen.
615     auto currentScreen = window->screen();
616     if (currentScreen && !currentScreen->geometry().contains(globalPosCandidate)) {
617         auto nativeGlobalPos = QHighDpi::toNativePixels(globalPosCandidate, currentScreen);
618         if (auto actualPlatformScreen = currentScreen->handle()->screenForPosition(nativeGlobalPos))
619             return QHighDpi::fromNativePixels(nativeGlobalPos, actualPlatformScreen->screen());
620     }
621 
622     return globalPosCandidate;
623 }
624 
mapPositionFromGlobal(const QPoint & pos,const QPoint & windowGlobalPosition,const QWindow * window)625 QPoint QHighDpiScaling::mapPositionFromGlobal(const QPoint &pos, const QPoint &windowGlobalPosition, const QWindow *window)
626 {
627     QPoint windowPosCandidate = pos - windowGlobalPosition;
628     if (QGuiApplicationPrivate::screen_list.size() <= 1 || window->handle() == nullptr)
629         return windowPosCandidate;
630 
631     // Device independent global (screen) space may discontiguous when high-dpi scaling
632     // is active. This means that the normal subtracting of the window global position from the
633     // position-to-be-mapped may not work in cases where a window spans multiple screens.
634     // Map both positions to native global space (using the correct screens), subtract there,
635     // and then map the difference back using the scale factor for the window.
636     QScreen *posScreen = QGuiApplication::screenAt(pos);
637     if (posScreen && posScreen != window->screen()) {
638         QPoint nativePos = QHighDpi::toNativePixels(pos, posScreen);
639         QPoint windowNativePos = window->handle()->geometry().topLeft();
640         return QHighDpi::fromNativeLocalPosition(nativePos - windowNativePos, window);
641     }
642 
643     return windowPosCandidate;
644 }
645 
screenSubfactor(const QPlatformScreen * screen)646 qreal QHighDpiScaling::screenSubfactor(const QPlatformScreen *screen)
647 {
648     auto factor = qreal(1.0);
649     if (!screen)
650         return factor;
651 
652     // Unlike the other code where factors are combined by multiplication,
653     // factors from QT_SCREEN_SCALE_FACTORS takes precedence over the factor
654     // computed from platform plugin DPI. The rationale is that the user is
655     // setting the factor to override erroneous DPI values.
656     bool screenPropertyUsed = false;
657     if (m_screenFactorSet) {
658         // Check if there is a factor set on the screen object or associated
659         // with the screen name. These are mutually exclusive, so checking
660         // order is not significant.
661         if (auto qScreen = screen->screen()) {
662             auto screenFactor = qScreen->property(scaleFactorProperty).toReal(&screenPropertyUsed);
663             if (screenPropertyUsed)
664                 factor = screenFactor;
665         }
666 
667         if (!screenPropertyUsed) {
668             auto byNameIt = qNamedScreenScaleFactors()->constFind(screen->name());
669             if ((screenPropertyUsed = byNameIt != qNamedScreenScaleFactors()->cend()))
670                 factor = *byNameIt;
671         }
672     }
673 
674     if (!screenPropertyUsed && m_usePixelDensity)
675         factor = roundScaleFactor(rawScaleFactor(screen));
676 
677     return factor;
678 }
679 
logicalDpi(const QScreen * screen)680 QDpi QHighDpiScaling::logicalDpi(const QScreen *screen)
681 {
682     // (Note: m_active test is performed at call site.)
683     if (!screen || !screen->handle())
684         return QDpi(96, 96);
685 
686     if (!m_usePixelDensity) {
687         const qreal screenScaleFactor = screenSubfactor(screen->handle());
688         const QDpi dpi = QPlatformScreen::overrideDpi(screen->handle()->logicalDpi());
689         return QDpi{ dpi.first / screenScaleFactor, dpi.second / screenScaleFactor };
690     }
691 
692     const qreal scaleFactor = rawScaleFactor(screen->handle());
693     const qreal roundedScaleFactor = roundScaleFactor(scaleFactor);
694     return effectiveLogicalDpi(screen->handle(), scaleFactor, roundedScaleFactor);
695 }
696 
scaleAndOrigin(const QPlatformScreen * platformScreen,QPoint * nativePosition)697 QHighDpiScaling::ScaleAndOrigin QHighDpiScaling::scaleAndOrigin(const QPlatformScreen *platformScreen, QPoint *nativePosition)
698 {
699     if (!m_active)
700         return { qreal(1), QPoint() };
701     if (!platformScreen)
702         return { m_factor, QPoint() }; // the global factor
703     const QPlatformScreen *actualScreen = nativePosition ?
704         platformScreen->screenForPosition(*nativePosition) : platformScreen;
705     return { m_factor * screenSubfactor(actualScreen), actualScreen->geometry().topLeft() };
706 }
707 
scaleAndOrigin(const QScreen * screen,QPoint * nativePosition)708 QHighDpiScaling::ScaleAndOrigin QHighDpiScaling::scaleAndOrigin(const QScreen *screen, QPoint *nativePosition)
709 {
710     if (!m_active)
711         return { qreal(1), QPoint() };
712     if (!screen)
713         return { m_factor, QPoint() }; // the global factor
714     return scaleAndOrigin(screen->handle(), nativePosition);
715 }
716 
scaleAndOrigin(const QWindow * window,QPoint * nativePosition)717 QHighDpiScaling::ScaleAndOrigin QHighDpiScaling::scaleAndOrigin(const QWindow *window, QPoint *nativePosition)
718 {
719     if (!m_active)
720         return { qreal(1), QPoint() };
721 
722     QScreen *screen = window ? window->screen() : QGuiApplication::primaryScreen();
723     const bool searchScreen = !window || window->isTopLevel();
724     return scaleAndOrigin(screen, searchScreen ? nativePosition : nullptr);
725 }
726 
727 #endif //QT_NO_HIGHDPISCALING
728 QT_END_NAMESPACE
729