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
53QT_BEGIN_NAMESPACE
54
55Q_LOGGING_CATEGORY(lcScaling, "qt.scaling");
56
57#ifndef QT_NO_HIGHDPISCALING
58
59static const char enableHighDpiScalingEnvVar[] = "QT_ENABLE_HIGHDPI_SCALING";
60static const char scaleFactorEnvVar[] = "QT_SCALE_FACTOR";
61static const char screenFactorsEnvVar[] = "QT_SCREEN_SCALE_FACTORS";
62static const char scaleFactorRoundingPolicyEnvVar[] = "QT_SCALE_FACTOR_ROUNDING_POLICY";
63static const char dpiAdjustmentPolicyEnvVar[] = "QT_DPI_ADJUSTMENT_POLICY";
64static const char usePhysicalDpiEnvVar[] = "QT_USE_PHYSICAL_DPI";
65
66// Per-screen scale factors for named screens set with QT_SCREEN_SCALE_FACTORS
67// are stored here. Use a global hash to keep the factor across screen
68// disconnect/connect cycles where the screen object may be deleted.
69typedef QHash<QString, qreal> QScreenScaleFactorHash;
70Q_GLOBAL_STATIC(QScreenScaleFactorHash, qNamedScreenScaleFactors);
71
72// Reads and interprets the given environment variable as a bool,
73// returns the default value if not set.
74static bool qEnvironmentVariableAsBool(const char *name, bool defaultValue)
75{
76 bool ok = false;
77 int value = qEnvironmentVariableIntValue(name, &ok);
78 return ok ? value > 0 : defaultValue;
79}
80
81static inline qreal initialGlobalScaleFactor()
82{
83
84 qreal result = 1;
85 if (qEnvironmentVariableIsSet(scaleFactorEnvVar)) {
86 bool ok;
87 const qreal f = qEnvironmentVariable(scaleFactorEnvVar).toDouble(&ok);
88 if (ok && f > 0) {
89 qCDebug(lcScaling) << "Apply " << scaleFactorEnvVar << f;
90 result = f;
91 }
92 }
93
94 return result;
95}
96
97/*!
98 \class QHighDpiScaling
99 \since 5.6
100 \internal
101 \preliminary
102 \ingroup qpa
103
104 \brief Collection of utility functions for UI scaling.
105
106 QHighDpiScaling implements utility functions for high-dpi scaling for use
107 on operating systems that provide limited support for native scaling, such
108 as Windows, X11, and Android. In addition this functionality can be used
109 for simulation and testing purposes.
110
111 The functions support scaling between the device independent coordinate
112 system used by Qt applications and the native coordinate system used by
113 the platform plugins. Intended usage locations are the low level / platform
114 plugin interfacing parts of QtGui, for example the QWindow, QScreen and
115 QWindowSystemInterface implementation.
116
117 There are now up to three active coordinate systems in Qt:
118
119 ---------------------------------------------------
120 | Application Device Independent Pixels | devicePixelRatio
121 | Qt Widgets | =
122 | Qt Gui |
123 |---------------------------------------------------| Qt Scale Factor
124 | Qt Gui QPlatform* Native Pixels | *
125 | Qt platform plugin |
126 |---------------------------------------------------| OS Scale Factor
127 | Display Device Pixels |
128 | (Graphics Buffers) |
129 -----------------------------------------------------
130
131 This is an simplification and shows the main coordinate system. All layers
132 may work with device pixels in specific cases: OpenGL, creating the backing
133 store, and QPixmap management. The "Native Pixels" coordinate system is
134 internal to Qt and should not be exposed to Qt users: Seen from the outside
135 there are only two coordinate systems: device independent pixels and device
136 pixels.
137
138 The devicePixelRatio seen by applications is the product of the Qt scale
139 factor and the OS scale factor (see QWindow::devicePixelRatio()). The value
140 of the scale factors may be 1, in which case two or more of the coordinate
141 systems are equivalent. Platforms that (may) have an OS scale factor include
142 macOS, iOS, Wayland, and Web(Assembly).
143
144 Note that the API implemented in this file do use the OS scale factor, and
145 is used for converting between device independent and native pixels only.
146
147 Configuration Examples:
148
149 'Classic': Device Independent Pixels = Native Pixels = Device Pixels
150 --------------------------------------------------- devicePixelRatio: 1
151 | Application / Qt Gui 100 x 100 |
152 | | Qt Scale Factor: 1
153 | Qt Platform / OS 100 x 100 |
154 | | OS Scale Factor: 1
155 | Display 100 x 100 |
156 -----------------------------------------------------
157
158 '2x Apple Device': Device Independent Pixels = Native Pixels
159 --------------------------------------------------- devicePixelRatio: 2
160 | Application / Qt Gui 100 x 100 |
161 | | Qt Scale Factor: 1
162 | Qt Platform / OS 100 x 100 |
163 |---------------------------------------------------| OS Scale Factor: 2
164 | Display 200 x 200 |
165 -----------------------------------------------------
166
167 'Windows at 200%': Native Pixels = Device Pixels
168 --------------------------------------------------- devicePixelRatio: 2
169 | Application / Qt Gui 100 x 100 |
170 |---------------------------------------------------| Qt Scale Factor: 2
171 | Qt Platform / OS 200 x 200 |
172 | | OS Scale Factor: 1
173 | Display 200 x 200 |
174 -----------------------------------------------------
175
176 * Configuration
177
178 - Enabling: In Qt 6, high-dpi scaling (the functionality implemented in this file)
179 is always enabled. The Qt scale factor value is typically determined by the
180 QPlatformScreen implementation - see below.
181
182 There is one environment variable based opt-out option: set QT_ENABLE_HIGH_DPI_SCALING=0.
183 Keep in mind that this does not affect the OS scale factor, which is controlled by
184 the operating system.
185
186 - Qt scale factor value: The Qt scale factor is the product of the screen scale
187 factor and the global scale factor, which are independently either set or determined
188 by the platform plugin. Several APIs are offered for this, targeting both developers
189 and end users. All scale factors are of type qreal.
190
191 1) Per-screen scale factors
192
193 Per-screen scale factors are computed based on logical DPI provided by
194 by the platform plugin.
195
196 The platform plugin implements DPI accessor functions:
197 QDpi QPlatformScreen::logicalDpi()
198 QDpi QPlatformScreen::logicalBaseDpi()
199
200 QHighDpiScaling then computes the per-screen scale factor as follows:
201
202 factor = logicalDpi / logicalBaseDpi
203
204 Alternatively, QT_SCREEN_SCALE_FACTORS can be used to set the screen
205 scale factors.
206
207 2) The global scale factor
208
209 The QT_SCALE_FACTOR environment variable can be used to set a global scale
210 factor which applies to all application windows. This allows developing and
211 testing at any DPR, independently of available hardware and without changing
212 global desktop settings.
213
214 - Rounding
215
216 Qt 6 does not round scale factors by default. Qt 5 rounds the screen scale factor
217 to the nearest integer (except for Qt on Android which does not round).
218
219 The rounding policy can be set by the application, or on the environment:
220
221 Application (C++): QGuiApplication::setHighDpiScaleFactorRoundingPolicy()
222 User (environment): QT_SCALE_FACTOR_ROUNDING_POLICY
223
224 Note that the OS scale factor, and global scale factors set with QT_SCALE_FACTOR
225 are never rounded by Qt.
226
227 * C++ API Overview
228
229 - Coordinate Conversion ("scaling")
230
231 The QHighDpi namespace provides several functions for converting geometry
232 between the device independent and native coordinate systems. These should
233 be used when calling "QPlatform*" API from QtGui. Callers are responsible
234 for selecting a function variant based on geometry type:
235
236 Type From Native To Native
237 local : QHighDpi::fromNativeLocalPosition() QHighDpi::toNativeLocalPosition()
238 global (screen) : QHighDpi::fromNativeGlobalPosition() QHighDpi::toNativeGlobalPosition()
239 QWindow::geometry() : QHighDpi::fromNativeWindowGeometry() QHighDpi::toNativeWindowGeometry()
240 sizes, margins, etc : QHighDpi::fromNativePixels() QHighDpi::toNativePixels()
241
242 The conversion functions take two arguments; the geometry and a context:
243
244 QSize nativeSize = toNativePixels(deviceIndependentSize, window);
245
246 The context is usually a QWindow instance, but can also be a QScreen instance,
247 or the corresponding QPlatform classes.
248
249 - Activation
250
251 QHighDpiScaling::isActive() returns true iff
252 Qt high-dpi scaling is enabled (e.g. with AA_EnableHighDpiScaling) AND
253 there is a Qt scale factor != 1
254
255 (the value of the OS scale factor does not affect this API)
256
257 - Calling QtGui from the platform plugins
258
259 Platform plugin code should be careful about calling QtGui geometry accessor
260 functions like geometry():
261
262 QRect r = window->geometry();
263
264 In this case the returned geometry is in the wrong coordinate system (device independent
265 instead of native pixels). Fix this by adding a conversion call:
266
267 QRect r = QHighDpi::toNativeWindowGeometry(window->geometry());
268
269 (Also consider if the call to QtGui is really needed - prefer calling QPlatform* API.)
270*/
271
272qreal QHighDpiScaling::m_factor = 1.0;
273bool QHighDpiScaling::m_active = false; //"overall active" - is there any scale factor set.
274bool QHighDpiScaling::m_usePlatformPluginDpi = false; // use scale factor based on platform plugin DPI
275bool QHighDpiScaling::m_platformPluginDpiScalingActive = false; // platform plugin DPI gives a scale factor > 1
276bool QHighDpiScaling::m_globalScalingActive = false; // global scale factor is active
277bool QHighDpiScaling::m_screenFactorSet = false; // QHighDpiScaling::setScreenFactor has been used
278
279/*
280 Initializes the QHighDpiScaling global variables. Called before the
281 platform plugin is created.
282*/
283
284static inline bool usePlatformPluginDpi()
285{
286 // Determine if we should set a scale factor based on the logical DPI
287 // reported by the platform plugin.
288
289 bool enableEnvValueOk;
290 const int enableEnvValue = qEnvironmentVariableIntValue(enableHighDpiScalingEnvVar, &enableEnvValueOk);
291 if (enableEnvValueOk && enableEnvValue < 1)
292 return false;
293
294 // Enable by default
295 return true;
296}
297
298qreal QHighDpiScaling::rawScaleFactor(const QPlatformScreen *screen)
299{
300 // Determine if physical DPI should be used
301 static const bool usePhysicalDpi = qEnvironmentVariableAsBool(usePhysicalDpiEnvVar, false);
302
303 // Calculate scale factor beased on platform screen DPI values
304 qreal factor;
305 QDpi platformBaseDpi = screen->logicalBaseDpi();
306 if (usePhysicalDpi) {
307 QSize sz = screen->geometry().size();
308 QSizeF psz = screen->physicalSize();
309 qreal platformPhysicalDpi = ((sz.height() / psz.height()) + (sz.width() / psz.width())) * qreal(25.4 * 0.5);
310 factor = qreal(platformPhysicalDpi) / qreal(platformBaseDpi.first);
311 } else {
312 const QDpi platformLogicalDpi = QPlatformScreen::overrideDpi(screen->logicalDpi());
313 factor = qreal(platformLogicalDpi.first) / qreal(platformBaseDpi.first);
314 }
315
316 return factor;
317}
318
319template <class EnumType>
320struct EnumLookup
321{
322 const char *name;
323 EnumType value;
324};
325
326template <class EnumType>
327static bool operator==(const EnumLookup<EnumType> &e1, const EnumLookup<EnumType> &e2)
328{
329 return qstricmp(e1.name, e2.name) == 0;
330}
331
332template <class EnumType>
333static QByteArray joinEnumValues(const EnumLookup<EnumType> *i1, const EnumLookup<EnumType> *i2)
334{
335 QByteArray result;
336 for (; i1 < i2; ++i1) {
337 if (!result.isEmpty())
338 result += QByteArrayLiteral(", ");
339 result += i1->name;
340 }
341 return result;
342}
343
344using ScaleFactorRoundingPolicyLookup = EnumLookup<Qt::HighDpiScaleFactorRoundingPolicy>;
345
346static const ScaleFactorRoundingPolicyLookup scaleFactorRoundingPolicyLookup[] =
347{
348 {"Round", Qt::HighDpiScaleFactorRoundingPolicy::Round},
349 {"Ceil", Qt::HighDpiScaleFactorRoundingPolicy::Ceil},
350 {"Floor", Qt::HighDpiScaleFactorRoundingPolicy::Floor},
351 {"RoundPreferFloor", Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor},
352 {"PassThrough", Qt::HighDpiScaleFactorRoundingPolicy::PassThrough}
353};
354
355static Qt::HighDpiScaleFactorRoundingPolicy
356 lookupScaleFactorRoundingPolicy(const QByteArray &v)
357{
358 auto end = std::end(scaleFactorRoundingPolicyLookup);
359 auto it = std::find(std::begin(scaleFactorRoundingPolicyLookup), end,
360 ScaleFactorRoundingPolicyLookup{v.constData(), Qt::HighDpiScaleFactorRoundingPolicy::Unset});
361 return it != end ? it->value : Qt::HighDpiScaleFactorRoundingPolicy::Unset;
362}
363
364using DpiAdjustmentPolicyLookup = EnumLookup<QHighDpiScaling::DpiAdjustmentPolicy>;
365
366static const DpiAdjustmentPolicyLookup dpiAdjustmentPolicyLookup[] =
367{
368 {"AdjustDpi", QHighDpiScaling::DpiAdjustmentPolicy::Enabled},
369 {"DontAdjustDpi", QHighDpiScaling::DpiAdjustmentPolicy::Disabled},
370 {"AdjustUpOnly", QHighDpiScaling::DpiAdjustmentPolicy::UpOnly}
371};
372
373static QHighDpiScaling::DpiAdjustmentPolicy
374 lookupDpiAdjustmentPolicy(const QByteArray &v)
375{
376 auto end = std::end(dpiAdjustmentPolicyLookup);
377 auto it = std::find(std::begin(dpiAdjustmentPolicyLookup), end,
378 DpiAdjustmentPolicyLookup{v.constData(), QHighDpiScaling::DpiAdjustmentPolicy::Unset});
379 return it != end ? it->value : QHighDpiScaling::DpiAdjustmentPolicy::Unset;
380}
381
382qreal QHighDpiScaling::roundScaleFactor(qreal rawFactor)
383{
384 // Apply scale factor rounding policy. Using mathematically correct rounding
385 // may not give the most desirable visual results, especially for
386 // critical fractions like .5. In general, rounding down results in visual
387 // sizes that are smaller than the ideal size, and opposite for rounding up.
388 // Rounding down is then preferable since "small UI" is a more acceptable
389 // high-DPI experience than "large UI".
390 static auto scaleFactorRoundingPolicy = Qt::HighDpiScaleFactorRoundingPolicy::Unset;
391
392 // Determine rounding policy
393 if (scaleFactorRoundingPolicy == Qt::HighDpiScaleFactorRoundingPolicy::Unset) {
394 // Check environment
395 if (qEnvironmentVariableIsSet(scaleFactorRoundingPolicyEnvVar)) {
396 QByteArray policyText = qgetenv(scaleFactorRoundingPolicyEnvVar);
397 auto policyEnumValue = lookupScaleFactorRoundingPolicy(policyText);
398 if (policyEnumValue != Qt::HighDpiScaleFactorRoundingPolicy::Unset) {
399 scaleFactorRoundingPolicy = policyEnumValue;
400 } else {
401 auto values = joinEnumValues(std::begin(scaleFactorRoundingPolicyLookup),
402 std::end(scaleFactorRoundingPolicyLookup));
403 qWarning("Unknown scale factor rounding policy: %s. Supported values are: %s.",
404 policyText.constData(), values.constData());
405 }
406 }
407
408 // Check application object if no environment value was set.
409 if (scaleFactorRoundingPolicy == Qt::HighDpiScaleFactorRoundingPolicy::Unset) {
410 scaleFactorRoundingPolicy = QGuiApplication::highDpiScaleFactorRoundingPolicy();
411 } else {
412 // Make application setting reflect environment
413 QGuiApplication::setHighDpiScaleFactorRoundingPolicy(scaleFactorRoundingPolicy);
414 }
415 }
416
417 // Apply rounding policy.
418 qreal roundedFactor = rawFactor;
419 switch (scaleFactorRoundingPolicy) {
420 case Qt::HighDpiScaleFactorRoundingPolicy::Round:
421 roundedFactor = qRound(rawFactor);
422 break;
423 case Qt::HighDpiScaleFactorRoundingPolicy::Ceil:
424 roundedFactor = qCeil(rawFactor);
425 break;
426 case Qt::HighDpiScaleFactorRoundingPolicy::Floor:
427 roundedFactor = qFloor(rawFactor);
428 break;
429 case Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor:
430 // Round up for .75 and higher. This favors "small UI" over "large UI".
431 roundedFactor = rawFactor - qFloor(rawFactor) < 0.75
432 ? qFloor(rawFactor) : qCeil(rawFactor);
433 break;
434 case Qt::HighDpiScaleFactorRoundingPolicy::PassThrough:
435 case Qt::HighDpiScaleFactorRoundingPolicy::Unset:
436 break;
437 }
438
439 // Don't round down to to zero; clamp the minimum (rounded) factor to 1.
440 // This is not a common case but can happen if a display reports a very
441 // low DPI.
442 if (scaleFactorRoundingPolicy != Qt::HighDpiScaleFactorRoundingPolicy::PassThrough)
443 roundedFactor = qMax(roundedFactor, qreal(1));
444
445 return roundedFactor;
446}
447
448QDpi QHighDpiScaling::effectiveLogicalDpi(const QPlatformScreen *screen, qreal rawFactor, qreal roundedFactor)
449{
450 // Apply DPI adjustment policy, if needed. If enabled this will change the
451 // reported logical DPI to account for the difference between the rounded
452 // scale factor and the actual scale factor. The effect is that text size
453 // will be correct for the screen dpi, but may be (slightly) out of sync
454 // with the rest of the UI. The amount of out-of-synch-ness depends on how
455 // well user code handles a non-standard DPI values, but since the
456 // adjustment is small (typically +/- 48 max) this might be OK.
457 static auto dpiAdjustmentPolicy = DpiAdjustmentPolicy::Unset;
458
459 // Determine adjustment policy.
460 if (dpiAdjustmentPolicy == DpiAdjustmentPolicy::Unset) {
461 if (qEnvironmentVariableIsSet(dpiAdjustmentPolicyEnvVar)) {
462 QByteArray policyText = qgetenv(dpiAdjustmentPolicyEnvVar);
463 auto policyEnumValue = lookupDpiAdjustmentPolicy(policyText);
464 if (policyEnumValue != DpiAdjustmentPolicy::Unset) {
465 dpiAdjustmentPolicy = policyEnumValue;
466 } else {
467 auto values = joinEnumValues(std::begin(dpiAdjustmentPolicyLookup),
468 std::end(dpiAdjustmentPolicyLookup));
469 qWarning("Unknown DPI adjustment policy: %s. Supported values are: %s.",
470 policyText.constData(), values.constData());
471 }
472 }
473 if (dpiAdjustmentPolicy == DpiAdjustmentPolicy::Unset)
474 dpiAdjustmentPolicy = DpiAdjustmentPolicy::UpOnly;
475 }
476
477 // Apply adjustment policy.
478 const QDpi baseDpi = screen->logicalBaseDpi();
479 const qreal dpiAdjustmentFactor = rawFactor / roundedFactor;
480
481 // Return the base DPI for cases where there is no adjustment
482 if (dpiAdjustmentPolicy == DpiAdjustmentPolicy::Disabled)
483 return baseDpi;
484 if (dpiAdjustmentPolicy == DpiAdjustmentPolicy::UpOnly && dpiAdjustmentFactor < 1)
485 return baseDpi;
486
487 return QDpi(baseDpi.first * dpiAdjustmentFactor, baseDpi.second * dpiAdjustmentFactor);
488}
489
490void QHighDpiScaling::initHighDpiScaling()
491{
492 // Determine if there is a global scale factor set.
493 m_factor = initialGlobalScaleFactor();
494 m_globalScalingActive = !qFuzzyCompare(m_factor, qreal(1));
495
496 m_usePlatformPluginDpi = usePlatformPluginDpi();
497
498 m_platformPluginDpiScalingActive = false; //set in updateHighDpiScaling below
499
500 m_active = m_globalScalingActive || m_usePlatformPluginDpi;
501}
502
503void QHighDpiScaling::updateHighDpiScaling()
504{
505 m_usePlatformPluginDpi = usePlatformPluginDpi();
506
507 if (m_usePlatformPluginDpi && !m_platformPluginDpiScalingActive ) {
508 const auto screens = QGuiApplication::screens();
509 for (QScreen *screen : screens) {
510 if (!qFuzzyCompare(screenSubfactor(screen->handle()), qreal(1))) {
511 m_platformPluginDpiScalingActive = true;
512 break;
513 }
514 }
515 }
516 if (qEnvironmentVariableIsSet(screenFactorsEnvVar)) {
517 int i = 0;
518 const QString spec = qEnvironmentVariable(screenFactorsEnvVar);
519 const auto specs = QStringView{spec}.split(u';');
520 for (const auto &spec : specs) {
521 int equalsPos = spec.lastIndexOf(QLatin1Char('='));
522 qreal factor = 0;
523 if (equalsPos > 0) {
524 // support "name=factor"
525 bool ok;
526 const auto name = spec.left(equalsPos);
527 factor = spec.mid(equalsPos + 1).toDouble(&ok);
528 if (ok && factor > 0 ) {
529 const auto screens = QGuiApplication::screens();
530 for (QScreen *s : screens) {
531 if (s->name() == name) {
532 setScreenFactor(s, factor);
533 break;
534 }
535 }
536 }
537 } else {
538 // listing screens in order
539 bool ok;
540 factor = spec.toDouble(&ok);
541 if (ok && factor > 0 && i < QGuiApplication::screens().count()) {
542 QScreen *screen = QGuiApplication::screens().at(i);
543 setScreenFactor(screen, factor);
544 }
545 }
546 ++i;
547 }
548 }
549 m_active = m_globalScalingActive || m_screenFactorSet || m_platformPluginDpiScalingActive ;
550}
551
552/*
553 Sets the global scale factor which is applied to all windows.
554*/
555void QHighDpiScaling::setGlobalFactor(qreal factor)
556{
557 if (qFuzzyCompare(factor, m_factor))
558 return;
559 if (!QGuiApplication::allWindows().isEmpty())
560 qWarning("QHighDpiScaling::setFactor: Should only be called when no windows exist.");
561
562 m_globalScalingActive = !qFuzzyCompare(factor, qreal(1));
563 m_factor = m_globalScalingActive ? factor : qreal(1);
564 m_active = m_globalScalingActive || m_screenFactorSet || m_platformPluginDpiScalingActive ;
565 const auto screens = QGuiApplication::screens();
566 for (QScreen *screen : screens)
567 screen->d_func()->updateHighDpi();
568}
569
570static const char scaleFactorProperty[] = "_q_scaleFactor";
571
572/*
573 Sets a per-screen scale factor.
574*/
575void QHighDpiScaling::setScreenFactor(QScreen *screen, qreal factor)
576{
577 if (!qFuzzyCompare(factor, qreal(1))) {
578 m_screenFactorSet = true;
579 m_active = true;
580 }
581
582 // Prefer associating the factor with screen name over the object
583 // since the screen object may be deleted on screen disconnects.
584 const QString name = screen->name();
585 if (name.isEmpty())
586 screen->setProperty(scaleFactorProperty, QVariant(factor));
587 else
588 qNamedScreenScaleFactors()->insert(name, factor);
589
590 // hack to force re-evaluation of screen geometry
591 if (screen->handle())
592 screen->d_func()->setPlatformScreen(screen->handle()); // updates geometries based on scale factor
593}
594
595QPoint QHighDpiScaling::mapPositionToNative(const QPoint &pos, const QPlatformScreen *platformScreen)
596{
597 if (!platformScreen)
598 return pos;
599 const qreal scaleFactor = factor(platformScreen);
600 const QPoint topLeft = platformScreen->geometry().topLeft();
601 return (pos - topLeft) * scaleFactor + topLeft;
602}
603
604QPoint QHighDpiScaling::mapPositionFromNative(const QPoint &pos, const QPlatformScreen *platformScreen)
605{
606 if (!platformScreen)
607 return pos;
608 const qreal scaleFactor = factor(platformScreen);
609 const QPoint topLeft = platformScreen->geometry().topLeft();
610 return (pos - topLeft) / scaleFactor + topLeft;
611}
612
613qreal QHighDpiScaling::screenSubfactor(const QPlatformScreen *screen)
614{
615 auto factor = qreal(1.0);
616 if (!screen)
617 return factor;
618
619 // Unlike the other code where factors are combined by multiplication,
620 // factors from QT_SCREEN_SCALE_FACTORS takes precedence over the factor
621 // computed from platform plugin DPI. The rationale is that the user is
622 // setting the factor to override erroneous DPI values.
623 bool screenPropertyUsed = false;
624 if (m_screenFactorSet) {
625 // Check if there is a factor set on the screen object or associated
626 // with the screen name. These are mutually exclusive, so checking
627 // order is not significant.
628 if (auto qScreen = screen->screen()) {
629 auto screenFactor = qScreen->property(scaleFactorProperty).toReal(&screenPropertyUsed);
630 if (screenPropertyUsed)
631 factor = screenFactor;
632 }
633
634 if (!screenPropertyUsed) {
635 auto byNameIt = qNamedScreenScaleFactors()->constFind(screen->name());
636 if ((screenPropertyUsed = byNameIt != qNamedScreenScaleFactors()->cend()))
637 factor = *byNameIt;
638 }
639 }
640
641 if (!screenPropertyUsed && m_usePlatformPluginDpi)
642 factor = roundScaleFactor(rawScaleFactor(screen));
643
644 return factor;
645}
646
647QDpi QHighDpiScaling::logicalDpi(const QScreen *screen)
648{
649 // (Note: m_active test is performed at call site.)
650 if (!screen || !screen->handle())
651 return QDpi(96, 96);
652
653 if (!m_usePlatformPluginDpi) {
654 const qreal screenScaleFactor = screenSubfactor(screen->handle());
655 const QDpi dpi = QPlatformScreen::overrideDpi(screen->handle()->logicalDpi());
656 return QDpi{ dpi.first / screenScaleFactor, dpi.second / screenScaleFactor };
657 }
658
659 const qreal scaleFactor = rawScaleFactor(screen->handle());
660 const qreal roundedScaleFactor = roundScaleFactor(scaleFactor);
661 return effectiveLogicalDpi(screen->handle(), scaleFactor, roundedScaleFactor);
662}
663
664// Returns the screen containing \a position, using \a guess as a starting point
665// for the search. \a guess might be nullptr. Returns nullptr if \a position is outside
666// of all screens.
667QScreen *QHighDpiScaling::screenForPosition(QHighDpiScaling::Point position, QScreen *guess)
668{
669 if (position.kind == QHighDpiScaling::Point::Invalid)
670 return nullptr;
671
672 auto getPlatformScreenGuess = [](QScreen *maybeScreen) -> QPlatformScreen * {
673 if (maybeScreen)
674 return maybeScreen->handle();
675 if (QScreen *primary = QGuiApplication::primaryScreen())
676 return primary->handle();
677 return nullptr;
678 };
679
680 QPlatformScreen *platformGuess = getPlatformScreenGuess(guess);
681 if (!platformGuess)
682 return nullptr;
683
684 auto onScreen = [](QHighDpiScaling::Point position, const QPlatformScreen *platformScreen) -> bool {
685 return position.kind == Point::Native
686 ? platformScreen->geometry().contains(position.point)
687 : platformScreen->screen()->geometry().contains(position.point);
688 };
689
690 // is the guessed screen correct?
691 if (onScreen(position, platformGuess))
692 return platformGuess->screen();
693
694 // search sibling screens
695 const auto screens = platformGuess->virtualSiblings();
696 for (const QPlatformScreen *screen : screens) {
697 if (onScreen(position, screen))
698 return screen->screen();
699 }
700
701 return nullptr;
702}
703
704QHighDpiScaling::ScaleAndOrigin QHighDpiScaling::scaleAndOrigin(const QPlatformScreen *platformScreen, QHighDpiScaling::Point position)
705{
706 Q_UNUSED(position)
707 if (!m_active)
708 return { qreal(1), QPoint() };
709 if (!platformScreen)
710 return { m_factor, QPoint() }; // the global factor
711 return { m_factor * screenSubfactor(platformScreen), platformScreen->geometry().topLeft() };
712}
713
714QHighDpiScaling::ScaleAndOrigin QHighDpiScaling::scaleAndOrigin(const QScreen *screen, QHighDpiScaling::Point position)
715{
716 Q_UNUSED(position)
717 if (!m_active)
718 return { qreal(1), QPoint() };
719 if (!screen)
720 return { m_factor, QPoint() }; // the global factor
721 return scaleAndOrigin(screen->handle(), position);
722}
723
724QHighDpiScaling::ScaleAndOrigin QHighDpiScaling::scaleAndOrigin(const QWindow *window, QHighDpiScaling::Point position)
725{
726 if (!m_active)
727 return { qreal(1), QPoint() };
728
729 // Determine correct screen; use the screen which contains the given
730 // position if a valid position is passed.
731 QScreen *screen = window ? window->screen() : QGuiApplication::primaryScreen();
732 QScreen *overrideScreen = QHighDpiScaling::screenForPosition(position, screen);
733 QScreen *targetScreen = overrideScreen ? overrideScreen : screen;
734 return scaleAndOrigin(targetScreen, position);
735}
736
737#else
738QHighDpiScaling::ScaleAndOrigin QHighDpiScaling::scaleAndOrigin(const QPlatformScreen *, QPoint *)
739{
740 return { qreal(1), QPoint() };
741}
742
743QHighDpiScaling::ScaleAndOrigin QHighDpiScaling::scaleAndOrigin(const QScreen *, QPoint *)
744{
745 return { qreal(1), QPoint() };
746}
747
748QHighDpiScaling::ScaleAndOrigin QHighDpiScaling::scaleAndOrigin(const QWindow *, QPoint *)
749{
750 return { qreal(1), QPoint() };
751}
752#endif //QT_NO_HIGHDPISCALING
753QT_END_NAMESPACE
754