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
58static 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.
62static const char legacyAutoScreenEnvVar[] = "QT_AUTO_SCREEN_SCALE_FACTOR";
63
64static const char enableHighDpiScalingEnvVar[] = "QT_ENABLE_HIGHDPI_SCALING";
65static const char scaleFactorEnvVar[] = "QT_SCALE_FACTOR";
66static const char screenFactorsEnvVar[] = "QT_SCREEN_SCALE_FACTORS";
67static const char scaleFactorRoundingPolicyEnvVar[] = "QT_SCALE_FACTOR_ROUNDING_POLICY";
68static const char dpiAdjustmentPolicyEnvVar[] = "QT_DPI_ADJUSTMENT_POLICY";
69static 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.
74typedef QHash<QString, qreal> QScreenScaleFactorHash;
75Q_GLOBAL_STATIC(QScreenScaleFactorHash, qNamedScreenScaleFactors);
76
77// Reads and interprets the given environment variable as a bool,
78// returns the default value if not set.
79static bool qEnvironmentVariableAsBool(const char *name, bool defaultValue)
80{
81 bool ok = false;
82 int value = qEnvironmentVariableIntValue(varName: name, ok: &ok);
83 return ok ? value > 0 : defaultValue;
84}
85
86static inline qreal initialGlobalScaleFactor()
87{
88
89 qreal result = 1;
90 if (qEnvironmentVariableIsSet(varName: scaleFactorEnvVar)) {
91 bool ok;
92 const qreal f = qEnvironmentVariable(varName: scaleFactorEnvVar).toDouble(ok: &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(varName: legacyDevicePixelEnvVar)) {
100 qWarning(msg: "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(varName: 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
250qreal QHighDpiScaling::m_factor = 1.0;
251bool QHighDpiScaling::m_active = false; //"overall active" - is there any scale factor set.
252bool QHighDpiScaling::m_usePixelDensity = false; // use scale factor from platform plugin
253bool QHighDpiScaling::m_pixelDensityScalingActive = false; // pixel density scale factor > 1
254bool QHighDpiScaling::m_globalScalingActive = false; // global scale factor is active
255bool 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
262static 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(attribute: Qt::AA_DisableHighDpiScaling))
270 return false;
271 bool screenEnvValueOk;
272 const int screenEnvValue = qEnvironmentVariableIntValue(varName: legacyAutoScreenEnvVar, ok: &screenEnvValueOk);
273 if (screenEnvValueOk && screenEnvValue < 1)
274 return false;
275 bool enableEnvValueOk;
276 const int enableEnvValue = qEnvironmentVariableIntValue(varName: enableHighDpiScalingEnvVar, ok: &enableEnvValueOk);
277 if (enableEnvValueOk && enableEnvValue < 1)
278 return false;
279
280 // Then return if there was an enable.
281 return QCoreApplication::testAttribute(attribute: Qt::AA_EnableHighDpiScaling)
282 || (screenEnvValueOk && screenEnvValue > 0)
283 || (enableEnvValueOk && enableEnvValue > 0)
284 || (qEnvironmentVariableIsSet(varName: legacyDevicePixelEnvVar)
285 && qEnvironmentVariable(varName: legacyDevicePixelEnvVar).compare(other: QLatin1String("auto"), cs: Qt::CaseInsensitive) == 0);
286}
287
288qreal QHighDpiScaling::rawScaleFactor(const QPlatformScreen *screen)
289{
290 // Determine if physical DPI should be used
291 static const bool usePhysicalDpi = qEnvironmentVariableAsBool(name: usePhysicalDpiEnvVar, defaultValue: 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(in: screen->logicalDpi());
303 factor = qreal(platformLogicalDpi.first) / qreal(platformBaseDpi.first);
304 }
305
306 return factor;
307}
308
309template <class EnumType>
310struct EnumLookup
311{
312 const char *name;
313 EnumType value;
314};
315
316template <class EnumType>
317static bool operator==(const EnumLookup<EnumType> &e1, const EnumLookup<EnumType> &e2)
318{
319 return qstricmp(e1.name, e2.name) == 0;
320}
321
322template <class EnumType>
323static 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
334using ScaleFactorRoundingPolicyLookup = EnumLookup<Qt::HighDpiScaleFactorRoundingPolicy>;
335
336static const ScaleFactorRoundingPolicyLookup scaleFactorRoundingPolicyLookup[] =
337{
338 {.name: "Round", .value: Qt::HighDpiScaleFactorRoundingPolicy::Round},
339 {.name: "Ceil", .value: Qt::HighDpiScaleFactorRoundingPolicy::Ceil},
340 {.name: "Floor", .value: Qt::HighDpiScaleFactorRoundingPolicy::Floor},
341 {.name: "RoundPreferFloor", .value: Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor},
342 {.name: "PassThrough", .value: Qt::HighDpiScaleFactorRoundingPolicy::PassThrough}
343};
344
345static Qt::HighDpiScaleFactorRoundingPolicy
346 lookupScaleFactorRoundingPolicy(const QByteArray &v)
347{
348 auto end = std::end(arr: scaleFactorRoundingPolicyLookup);
349 auto it = std::find(first: std::begin(arr: scaleFactorRoundingPolicyLookup), last: end,
350 val: ScaleFactorRoundingPolicyLookup{.name: v.constData(), .value: Qt::HighDpiScaleFactorRoundingPolicy::Unset});
351 return it != end ? it->value : Qt::HighDpiScaleFactorRoundingPolicy::Unset;
352}
353
354using DpiAdjustmentPolicyLookup = EnumLookup<QHighDpiScaling::DpiAdjustmentPolicy>;
355
356static const DpiAdjustmentPolicyLookup dpiAdjustmentPolicyLookup[] =
357{
358 {.name: "AdjustDpi", .value: QHighDpiScaling::DpiAdjustmentPolicy::Enabled},
359 {.name: "DontAdjustDpi", .value: QHighDpiScaling::DpiAdjustmentPolicy::Disabled},
360 {.name: "AdjustUpOnly", .value: QHighDpiScaling::DpiAdjustmentPolicy::UpOnly}
361};
362
363static QHighDpiScaling::DpiAdjustmentPolicy
364 lookupDpiAdjustmentPolicy(const QByteArray &v)
365{
366 auto end = std::end(arr: dpiAdjustmentPolicyLookup);
367 auto it = std::find(first: std::begin(arr: dpiAdjustmentPolicyLookup), last: end,
368 val: DpiAdjustmentPolicyLookup{.name: v.constData(), .value: QHighDpiScaling::DpiAdjustmentPolicy::Unset});
369 return it != end ? it->value : QHighDpiScaling::DpiAdjustmentPolicy::Unset;
370}
371
372qreal 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(varName: scaleFactorRoundingPolicyEnvVar)) {
386 QByteArray policyText = qgetenv(varName: scaleFactorRoundingPolicyEnvVar);
387 auto policyEnumValue = lookupScaleFactorRoundingPolicy(v: policyText);
388 if (policyEnumValue != Qt::HighDpiScaleFactorRoundingPolicy::Unset) {
389 scaleFactorRoundingPolicy = policyEnumValue;
390 } else {
391 auto values = joinEnumValues(i1: std::begin(arr: scaleFactorRoundingPolicyLookup),
392 i2: std::end(arr: scaleFactorRoundingPolicyLookup));
393 qWarning(msg: "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(d: rawFactor);
412 break;
413 case Qt::HighDpiScaleFactorRoundingPolicy::Ceil:
414 roundedFactor = qCeil(v: rawFactor);
415 break;
416 case Qt::HighDpiScaleFactorRoundingPolicy::Floor:
417 roundedFactor = qFloor(v: 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(v: rawFactor) < 0.75
422 ? qFloor(v: rawFactor) : qCeil(v: 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(a: roundedFactor, b: qreal(1));
434
435 return roundedFactor;
436}
437
438QDpi 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(varName: dpiAdjustmentPolicyEnvVar)) {
452 QByteArray policyText = qgetenv(varName: dpiAdjustmentPolicyEnvVar);
453 auto policyEnumValue = lookupDpiAdjustmentPolicy(v: policyText);
454 if (policyEnumValue != DpiAdjustmentPolicy::Unset) {
455 dpiAdjustmentPolicy = policyEnumValue;
456 } else {
457 auto values = joinEnumValues(i1: std::begin(arr: dpiAdjustmentPolicyLookup),
458 i2: std::end(arr: dpiAdjustmentPolicyLookup));
459 qWarning(msg: "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
480void QHighDpiScaling::initHighDpiScaling()
481{
482 // Determine if there is a global scale factor set.
483 m_factor = initialGlobalScaleFactor();
484 m_globalScalingActive = !qFuzzyCompare(p1: m_factor, p2: 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
493void QHighDpiScaling::updateHighDpiScaling()
494{
495 if (QCoreApplication::testAttribute(attribute: 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(p1: screenSubfactor(screen: screen->handle()), p2: qreal(1))) {
504 m_pixelDensityScalingActive = true;
505 break;
506 }
507 }
508 }
509 if (qEnvironmentVariableIsSet(varName: screenFactorsEnvVar)) {
510 int i = 0;
511 const QString spec = qEnvironmentVariable(varName: screenFactorsEnvVar);
512 const auto specs = spec.splitRef(sep: QLatin1Char(';'));
513 for (const QStringRef &spec : specs) {
514 int equalsPos = spec.lastIndexOf(ch: QLatin1Char('='));
515 qreal factor = 0;
516 if (equalsPos > 0) {
517 // support "name=factor"
518 bool ok;
519 const auto name = spec.left(n: equalsPos);
520 factor = spec.mid(pos: equalsPos + 1).toDouble(ok: &ok);
521 if (ok && factor > 0 ) {
522 const auto screens = QGuiApplication::screens();
523 for (QScreen *s : screens) {
524 if (s->name() == name) {
525 setScreenFactor(screen: s, factor);
526 break;
527 }
528 }
529 }
530 } else {
531 // listing screens in order
532 bool ok;
533 factor = spec.toDouble(ok: &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*/
548void QHighDpiScaling::setGlobalFactor(qreal factor)
549{
550 if (qFuzzyCompare(p1: factor, p2: m_factor))
551 return;
552 if (!QGuiApplication::allWindows().isEmpty())
553 qWarning(msg: "QHighDpiScaling::setFactor: Should only be called when no windows exist.");
554
555 m_globalScalingActive = !qFuzzyCompare(p1: factor, p2: 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
563static const char scaleFactorProperty[] = "_q_scaleFactor";
564
565/*
566 Sets a per-screen scale factor.
567*/
568void QHighDpiScaling::setScreenFactor(QScreen *screen, qreal factor)
569{
570 if (!qFuzzyCompare(p1: factor, p2: 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(name: scaleFactorProperty, value: QVariant(factor));
580 else
581 qNamedScreenScaleFactors()->insert(akey: name, avalue: 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
588QPoint QHighDpiScaling::mapPositionToNative(const QPoint &pos, const QPlatformScreen *platformScreen)
589{
590 if (!platformScreen)
591 return pos;
592 const qreal scaleFactor = factor(context: platformScreen);
593 const QPoint topLeft = platformScreen->geometry().topLeft();
594 return (pos - topLeft) * scaleFactor + topLeft;
595}
596
597QPoint QHighDpiScaling::mapPositionFromNative(const QPoint &pos, const QPlatformScreen *platformScreen)
598{
599 if (!platformScreen)
600 return pos;
601 const qreal scaleFactor = factor(context: platformScreen);
602 const QPoint topLeft = platformScreen->geometry().topLeft();
603 return (pos - topLeft) / scaleFactor + topLeft;
604}
605
606QPoint 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(p: globalPosCandidate)) {
617 auto nativeGlobalPos = QHighDpi::toNativePixels(value: globalPosCandidate, context: currentScreen);
618 if (auto actualPlatformScreen = currentScreen->handle()->screenForPosition(point: nativeGlobalPos))
619 return QHighDpi::fromNativePixels(value: nativeGlobalPos, context: actualPlatformScreen->screen());
620 }
621
622 return globalPosCandidate;
623}
624
625QPoint 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(point: pos);
637 if (posScreen && posScreen != window->screen()) {
638 QPoint nativePos = QHighDpi::toNativePixels(value: pos, context: posScreen);
639 QPoint windowNativePos = window->handle()->geometry().topLeft();
640 return QHighDpi::fromNativeLocalPosition(value: nativePos - windowNativePos, context: window);
641 }
642
643 return windowPosCandidate;
644}
645
646qreal 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(name: scaleFactorProperty).toReal(ok: &screenPropertyUsed);
663 if (screenPropertyUsed)
664 factor = screenFactor;
665 }
666
667 if (!screenPropertyUsed) {
668 auto byNameIt = qNamedScreenScaleFactors()->constFind(akey: screen->name());
669 if ((screenPropertyUsed = byNameIt != qNamedScreenScaleFactors()->cend()))
670 factor = *byNameIt;
671 }
672 }
673
674 if (!screenPropertyUsed && m_usePixelDensity)
675 factor = roundScaleFactor(rawFactor: rawScaleFactor(screen));
676
677 return factor;
678}
679
680QDpi 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: screen->handle());
688 const QDpi dpi = QPlatformScreen::overrideDpi(in: screen->handle()->logicalDpi());
689 return QDpi{ dpi.first / screenScaleFactor, dpi.second / screenScaleFactor };
690 }
691
692 const qreal scaleFactor = rawScaleFactor(screen: screen->handle());
693 const qreal roundedScaleFactor = roundScaleFactor(rawFactor: scaleFactor);
694 return effectiveLogicalDpi(screen: screen->handle(), rawFactor: scaleFactor, roundedFactor: roundedScaleFactor);
695}
696
697QHighDpiScaling::ScaleAndOrigin QHighDpiScaling::scaleAndOrigin(const QPlatformScreen *platformScreen, QPoint *nativePosition)
698{
699 if (!m_active)
700 return { .factor: qreal(1), .origin: QPoint() };
701 if (!platformScreen)
702 return { .factor: m_factor, .origin: QPoint() }; // the global factor
703 const QPlatformScreen *actualScreen = nativePosition ?
704 platformScreen->screenForPosition(point: *nativePosition) : platformScreen;
705 return { .factor: m_factor * screenSubfactor(screen: actualScreen), .origin: actualScreen->geometry().topLeft() };
706}
707
708QHighDpiScaling::ScaleAndOrigin QHighDpiScaling::scaleAndOrigin(const QScreen *screen, QPoint *nativePosition)
709{
710 if (!m_active)
711 return { .factor: qreal(1), .origin: QPoint() };
712 if (!screen)
713 return { .factor: m_factor, .origin: QPoint() }; // the global factor
714 return scaleAndOrigin(platformScreen: screen->handle(), nativePosition);
715}
716
717QHighDpiScaling::ScaleAndOrigin QHighDpiScaling::scaleAndOrigin(const QWindow *window, QPoint *nativePosition)
718{
719 if (!m_active)
720 return { .factor: qreal(1), .origin: QPoint() };
721
722 QScreen *screen = window ? window->screen() : QGuiApplication::primaryScreen();
723 const bool searchScreen = !window || window->isTopLevel();
724 return scaleAndOrigin(screen, nativePosition: searchScreen ? nativePosition : nullptr);
725}
726
727#endif //QT_NO_HIGHDPISCALING
728QT_END_NAMESPACE
729

source code of qtbase/src/gui/kernel/qhighdpiscaling.cpp