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