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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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