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

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