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