Coverage Report

Created: 2026-05-31 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/qtbase/src/gui/kernel/qhighdpiscaling.cpp
Line
Count
Source
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
5
#include "qhighdpiscaling_p.h"
6
#include "qguiapplication.h"
7
#include "qscreen.h"
8
#include "qplatformintegration.h"
9
#include "qplatformwindow.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
19
QT_BEGIN_NAMESPACE
20
21
Q_LOGGING_CATEGORY(lcHighDpi, "qt.highdpi");
22
23
#ifndef QT_NO_HIGHDPISCALING
24
25
static const char enableHighDpiScalingEnvVar[] = "QT_ENABLE_HIGHDPI_SCALING";
26
static const char scaleFactorEnvVar[] = "QT_SCALE_FACTOR";
27
static const char screenFactorsEnvVar[] = "QT_SCREEN_SCALE_FACTORS";
28
static const char scaleFactorRoundingPolicyEnvVar[] = "QT_SCALE_FACTOR_ROUNDING_POLICY";
29
static const char dpiAdjustmentPolicyEnvVar[] = "QT_DPI_ADJUSTMENT_POLICY";
30
static const char usePhysicalDpiEnvVar[] = "QT_USE_PHYSICAL_DPI";
31
32
[[maybe_unused]]
33
static std::optional<QString> qEnvironmentVariableOptionalString(const char *name)
34
0
{
35
0
    QString value = qEnvironmentVariable(name);
36
0
    return value.isNull() ? std::nullopt : std::optional(std::move(value));
37
0
}
38
39
static std::optional<QByteArray> qEnvironmentVariableOptionalByteArray(const char *name)
40
289k
{
41
289k
    QByteArray value = qgetenv(name);
42
289k
    return value.isNull() ? std::nullopt : std::optional(std::move(value));
43
289k
}
44
45
static std::optional<qreal> qEnvironmentVariableOptionalReal(const char *name)
46
144k
{
47
144k
    const QByteArray val = qgetenv(name);
48
144k
    if (val.isNull())
49
144k
        return std::nullopt;
50
51
0
    bool ok = false;
52
0
    const qreal value = val.toDouble(&ok);
53
0
    return ok ? std::optional(value) : std::nullopt;
54
144k
}
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
231
qreal QHighDpiScaling::m_factor = 1.0;
232
bool QHighDpiScaling::m_active = false; //"overall active" - is there any scale factor set.
233
bool QHighDpiScaling::m_usePlatformPluginDpi = false; // use scale factor based on platform plugin DPI
234
bool QHighDpiScaling::m_platformPluginDpiScalingActive  = false; // platform plugin DPI gives a scale factor > 1
235
bool QHighDpiScaling::m_globalScalingActive = false; // global scale factor is active
236
bool QHighDpiScaling::m_screenFactorSet = false; // QHighDpiScaling::setScreenFactor has been used
237
bool QHighDpiScaling::m_usePhysicalDpi = false;
238
QVector<QHighDpiScaling::ScreenFactor> QHighDpiScaling::m_screenFactors;
239
QHighDpiScaling::DpiAdjustmentPolicy QHighDpiScaling::m_dpiAdjustmentPolicy = QHighDpiScaling::DpiAdjustmentPolicy::Unset;
240
QHash<QString, qreal> QHighDpiScaling::m_namedScreenScaleFactors; // Per-screen scale factors (screen name -> factor)
241
242
qreal QHighDpiScaling::rawScaleFactor(const QPlatformScreen *screen)
243
289k
{
244
    // Calculate scale factor beased on platform screen DPI values
245
289k
    qreal factor;
246
289k
    QDpi platformBaseDpi = screen->logicalBaseDpi();
247
289k
    if (QHighDpiScaling::m_usePhysicalDpi) {
248
0
        QSize sz = screen->geometry().size();
249
0
        QSizeF psz = screen->physicalSize();
250
0
        qreal platformPhysicalDpi = ((sz.height() / psz.height()) + (sz.width() / psz.width())) * qreal(25.4 * 0.5);
251
0
        factor = qRound(platformPhysicalDpi) / qreal(platformBaseDpi.first);
252
289k
    } else {
253
289k
        const QDpi platformLogicalDpi = QPlatformScreen::overrideDpi(screen->logicalDpi());
254
289k
        factor = qreal(platformLogicalDpi.first) / qreal(platformBaseDpi.first);
255
289k
    }
256
257
289k
    return factor;
258
289k
}
259
260
template <class EnumType>
261
struct EnumLookup
262
{
263
    const char *name;
264
    EnumType value;
265
};
266
267
template <class EnumType>
268
static bool operator==(const EnumLookup<EnumType> &e1, const EnumLookup<EnumType> &e2)
269
0
{
270
0
    return qstricmp(e1.name, e2.name) == 0;
271
0
}
Unexecuted instantiation: qhighdpiscaling.cpp:bool operator==<Qt::HighDpiScaleFactorRoundingPolicy>(EnumLookup<Qt::HighDpiScaleFactorRoundingPolicy> const&, EnumLookup<Qt::HighDpiScaleFactorRoundingPolicy> const&)
Unexecuted instantiation: qhighdpiscaling.cpp:bool operator==<QHighDpiScaling::DpiAdjustmentPolicy>(EnumLookup<QHighDpiScaling::DpiAdjustmentPolicy> const&, EnumLookup<QHighDpiScaling::DpiAdjustmentPolicy> const&)
272
273
template <class EnumType>
274
static QByteArray joinEnumValues(const EnumLookup<EnumType> *i1, const EnumLookup<EnumType> *i2)
275
0
{
276
0
    QByteArray result;
277
0
    for (; i1 < i2; ++i1) {
278
0
        if (!result.isEmpty())
279
0
            result += QByteArrayLiteral(", ");
280
0
        result += i1->name;
281
0
    }
282
0
    return result;
283
0
}
Unexecuted instantiation: qhighdpiscaling.cpp:QByteArray joinEnumValues<Qt::HighDpiScaleFactorRoundingPolicy>(EnumLookup<Qt::HighDpiScaleFactorRoundingPolicy> const*, EnumLookup<Qt::HighDpiScaleFactorRoundingPolicy> const*)
Unexecuted instantiation: qhighdpiscaling.cpp:QByteArray joinEnumValues<QHighDpiScaling::DpiAdjustmentPolicy>(EnumLookup<QHighDpiScaling::DpiAdjustmentPolicy> const*, EnumLookup<QHighDpiScaling::DpiAdjustmentPolicy> const*)
284
285
using ScaleFactorRoundingPolicyLookup = EnumLookup<Qt::HighDpiScaleFactorRoundingPolicy>;
286
287
static const ScaleFactorRoundingPolicyLookup scaleFactorRoundingPolicyLookup[] =
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
296
static Qt::HighDpiScaleFactorRoundingPolicy
297
    lookupScaleFactorRoundingPolicy(const QByteArray &v)
298
0
{
299
0
    auto end = std::end(scaleFactorRoundingPolicyLookup);
300
0
    auto it = std::find(std::begin(scaleFactorRoundingPolicyLookup), end,
301
0
                        ScaleFactorRoundingPolicyLookup{v.constData(), Qt::HighDpiScaleFactorRoundingPolicy::Unset});
302
0
    return it != end ? it->value : Qt::HighDpiScaleFactorRoundingPolicy::Unset;
303
0
}
304
305
using DpiAdjustmentPolicyLookup = EnumLookup<QHighDpiScaling::DpiAdjustmentPolicy>;
306
307
static const DpiAdjustmentPolicyLookup dpiAdjustmentPolicyLookup[] =
308
{
309
    {"AdjustDpi", QHighDpiScaling::DpiAdjustmentPolicy::Enabled},
310
    {"DontAdjustDpi", QHighDpiScaling::DpiAdjustmentPolicy::Disabled},
311
    {"AdjustUpOnly", QHighDpiScaling::DpiAdjustmentPolicy::UpOnly}
312
};
313
314
static QHighDpiScaling::DpiAdjustmentPolicy
315
    lookupDpiAdjustmentPolicy(const QByteArray &v)
316
0
{
317
0
    auto end = std::end(dpiAdjustmentPolicyLookup);
318
0
    auto it = std::find(std::begin(dpiAdjustmentPolicyLookup), end,
319
0
                        DpiAdjustmentPolicyLookup{v.constData(), QHighDpiScaling::DpiAdjustmentPolicy::Unset});
320
0
    return it != end ? it->value : QHighDpiScaling::DpiAdjustmentPolicy::Unset;
321
0
}
322
323
qreal QHighDpiScaling::roundScaleFactor(qreal rawFactor)
324
289k
{
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
289k
    Qt::HighDpiScaleFactorRoundingPolicy scaleFactorRoundingPolicy =
333
289k
        QGuiApplication::highDpiScaleFactorRoundingPolicy();
334
335
    // Apply rounding policy.
336
289k
    qreal roundedFactor = rawFactor;
337
289k
    switch (scaleFactorRoundingPolicy) {
338
0
    case Qt::HighDpiScaleFactorRoundingPolicy::Round:
339
0
        roundedFactor = qRound(rawFactor);
340
0
        break;
341
0
    case Qt::HighDpiScaleFactorRoundingPolicy::Ceil:
342
0
        roundedFactor = qCeil(rawFactor);
343
0
        break;
344
0
    case Qt::HighDpiScaleFactorRoundingPolicy::Floor:
345
0
        roundedFactor = qFloor(rawFactor);
346
0
        break;
347
0
    case Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor:
348
        // Round up for .75 and higher. This favors "small UI" over "large UI".
349
0
        roundedFactor = rawFactor - qFloor(rawFactor) < 0.75
350
0
            ? qFloor(rawFactor) : qCeil(rawFactor);
351
0
        break;
352
289k
    case Qt::HighDpiScaleFactorRoundingPolicy::PassThrough:
353
289k
    case Qt::HighDpiScaleFactorRoundingPolicy::Unset:
354
289k
        break;
355
289k
    }
356
357
    // Clamp the minimum factor to 1. Qt does not currently render
358
    // correctly with factors less than 1.
359
289k
    roundedFactor = qMax(roundedFactor, qreal(1));
360
361
289k
    return roundedFactor;
362
289k
}
363
364
QDpi QHighDpiScaling::effectiveLogicalDpi(const QPlatformScreen *screen, qreal rawFactor, qreal roundedFactor)
365
0
{
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
0
    const QDpi baseDpi = screen->logicalBaseDpi();
376
0
    const qreal dpiAdjustmentFactor = rawFactor / roundedFactor;
377
378
    // Return the base DPI for cases where there is no adjustment
379
0
    if (QHighDpiScaling::m_dpiAdjustmentPolicy == DpiAdjustmentPolicy::Disabled)
380
0
        return baseDpi;
381
0
    if (QHighDpiScaling::m_dpiAdjustmentPolicy == DpiAdjustmentPolicy::UpOnly && dpiAdjustmentFactor < 1)
382
0
        return baseDpi;
383
384
0
    return QDpi(baseDpi.first * dpiAdjustmentFactor, baseDpi.second * dpiAdjustmentFactor);
385
0
}
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
*/
393
void QHighDpiScaling::initHighDpiScaling()
394
144k
{
395
144k
    qCDebug(lcHighDpi) << "Initializing high-DPI scaling";
396
397
    // Read environment variables
398
144k
    static const char* envDebugStr = "environment variable set:";
399
144k
    std::optional envEnableHighDpiScaling = qEnvironmentVariableIntegerValue(enableHighDpiScalingEnvVar);
400
144k
    if (envEnableHighDpiScaling.has_value())
401
144k
        qCDebug(lcHighDpi) << envDebugStr << enableHighDpiScalingEnvVar << envEnableHighDpiScaling.value();
402
403
144k
    std::optional<qreal> envScaleFactor = qEnvironmentVariableOptionalReal(scaleFactorEnvVar);
404
144k
    if (envScaleFactor.has_value())
405
144k
        qCDebug(lcHighDpi) << envDebugStr <<  scaleFactorEnvVar << envScaleFactor.value();
406
407
144k
    const QString envScreenFactors = qEnvironmentVariable(screenFactorsEnvVar);
408
144k
    if (envScreenFactors.isNull())
409
144k
        qCDebug(lcHighDpi) << envDebugStr << screenFactorsEnvVar << envScreenFactors;
410
411
144k
    std::optional envUsePhysicalDpi = qEnvironmentVariableIntegerValue(usePhysicalDpiEnvVar);
412
144k
    if (envUsePhysicalDpi.has_value())
413
144k
        qCDebug(lcHighDpi) << envDebugStr << usePhysicalDpiEnvVar << envUsePhysicalDpi.value();
414
415
144k
    std::optional<QByteArray> envScaleFactorRoundingPolicy = qEnvironmentVariableOptionalByteArray(scaleFactorRoundingPolicyEnvVar);
416
144k
    if (envScaleFactorRoundingPolicy.has_value())
417
144k
        qCDebug(lcHighDpi) << envDebugStr << scaleFactorRoundingPolicyEnvVar << envScaleFactorRoundingPolicy.value();
418
419
144k
    std::optional<QByteArray> envDpiAdjustmentPolicy = qEnvironmentVariableOptionalByteArray(dpiAdjustmentPolicyEnvVar);
420
144k
    if (envDpiAdjustmentPolicy.has_value())
421
144k
        qCDebug(lcHighDpi) << envDebugStr << dpiAdjustmentPolicyEnvVar << envDpiAdjustmentPolicy.value();
422
423
    // High-dpi scaling is enabled by default; check for global disable.
424
144k
    m_usePlatformPluginDpi = envEnableHighDpiScaling.value_or(1) > 0;
425
144k
    m_platformPluginDpiScalingActive = false; // see updateHighDpiScaling()
426
427
    // Check for glabal scale factor (different from 1)
428
144k
    m_factor = envScaleFactor.value_or(qreal(1));
429
144k
    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
144k
    m_screenFactors = parseScreenScaleFactorsSpec(envScreenFactors);
435
144k
    m_namedScreenScaleFactors.clear();
436
437
144k
    m_usePhysicalDpi = envUsePhysicalDpi.value_or(0) > 0;
438
439
    // Resolve HighDpiScaleFactorRoundingPolicy to QGuiApplication::highDpiScaleFactorRoundingPolicy
440
144k
    if (envScaleFactorRoundingPolicy.has_value()) {
441
0
        QByteArray policyText = envScaleFactorRoundingPolicy.value();
442
0
        auto policyEnumValue = lookupScaleFactorRoundingPolicy(policyText);
443
0
        if (policyEnumValue != Qt::HighDpiScaleFactorRoundingPolicy::Unset) {
444
            // set directly to avoid setHighDpiScaleFactorRoundingPolicy() warning
445
0
            QGuiApplicationPrivate::highDpiScaleFactorRoundingPolicy = policyEnumValue;
446
0
        } else {
447
0
            auto values = joinEnumValues(std::begin(scaleFactorRoundingPolicyLookup),
448
0
                                         std::end(scaleFactorRoundingPolicyLookup));
449
0
            qWarning("Unknown scale factor rounding policy: %s. Supported values are: %s.",
450
0
                     policyText.constData(), values.constData());
451
0
        }
452
0
    }
453
454
    // Resolve DpiAdjustmentPolicy to m_dpiAdjustmentPolicy
455
144k
    if (envDpiAdjustmentPolicy.has_value()) {
456
0
        QByteArray policyText = envDpiAdjustmentPolicy.value();
457
0
        auto policyEnumValue = lookupDpiAdjustmentPolicy(policyText);
458
0
        if (policyEnumValue != DpiAdjustmentPolicy::Unset) {
459
0
            QHighDpiScaling::m_dpiAdjustmentPolicy = policyEnumValue;
460
0
        } else {
461
0
            auto values = joinEnumValues(std::begin(dpiAdjustmentPolicyLookup),
462
0
                                         std::end(dpiAdjustmentPolicyLookup));
463
0
            qWarning("Unknown DPI adjustment policy: %s. Supported values are: %s.",
464
0
                     policyText.constData(), values.constData());
465
0
        }
466
0
    }
467
468
    // Set initial active state
469
144k
    m_active = m_globalScalingActive || m_usePlatformPluginDpi;
470
471
144k
    qCDebug(lcHighDpi) << "Initialization done, high-DPI scaling is"
472
0
                       << (m_active ? "active" : "inactive");
473
144k
}
474
475
/*
476
    Update configuration based on available screens and screen properties.
477
    This function may be called whenever the screen configuration changed.
478
*/
479
void QHighDpiScaling::updateHighDpiScaling()
480
144k
{
481
144k
    qCDebug(lcHighDpi) << "Updating high-DPI scaling";
482
483
    // Apply screen factors from environment
484
144k
    if (m_screenFactors.size() > 0) {
485
0
        qCDebug(lcHighDpi) << "Applying screen factors" << m_screenFactors;
486
0
        int i = -1;
487
0
        const auto screens = QGuiApplication::screens();
488
0
        for (const auto &[name, rawFactor]: m_screenFactors) {
489
0
            const qreal factor = roundScaleFactor(rawFactor);
490
0
            ++i;
491
0
            if (name.isNull()) {
492
0
                if (i < screens.size())
493
0
                    setScreenFactor(screens.at(i), factor);
494
0
            } else {
495
0
                for (QScreen *screen : screens) {
496
0
                    if (screen->name() == name) {
497
0
                        setScreenFactor(screen, factor);
498
0
                        break;
499
0
                    }
500
0
                }
501
0
            }
502
0
        }
503
0
    }
504
505
    // Check if any screens (now) has a scale factor != 1 and set
506
    // m_platformPluginDpiScalingActive if so.
507
144k
    if (m_usePlatformPluginDpi && !m_platformPluginDpiScalingActive ) {
508
144k
        const auto screens = QGuiApplication::screens();
509
144k
        for (QScreen *screen : screens) {
510
144k
            if (!qFuzzyCompare(screenSubfactor(screen->handle()), qreal(1))) {
511
0
                m_platformPluginDpiScalingActive  = true;
512
0
                break;
513
0
            }
514
144k
        }
515
144k
    }
516
517
144k
    m_active = m_globalScalingActive || m_screenFactorSet || m_platformPluginDpiScalingActive;
518
519
144k
    qCDebug(lcHighDpi) << "Update done, high-DPI scaling is"
520
0
                       << (m_active ? "active" : "inactive");
521
144k
}
522
523
/*
524
    Sets the global scale factor which is applied to all windows.
525
*/
526
void QHighDpiScaling::setGlobalFactor(qreal factor)
527
0
{
528
0
    qCDebug(lcHighDpi) << "Setting global scale factor to" << factor;
529
530
0
    if (qFuzzyCompare(factor, m_factor))
531
0
        return;
532
0
    if (!QGuiApplication::allWindows().isEmpty())
533
0
        qWarning("QHighDpiScaling::setFactor: Should only be called when no windows exist.");
534
535
0
    const auto screens = QGuiApplication::screens();
536
537
0
    std::vector<QScreenPrivate::UpdateEmitter> updateEmitters;
538
0
    for (QScreen *screen : screens)
539
0
        updateEmitters.emplace_back(screen);
540
541
0
    m_globalScalingActive = !qFuzzyCompare(factor, qreal(1));
542
0
    m_factor = m_globalScalingActive ? factor : qreal(1);
543
0
    m_active = m_globalScalingActive || m_screenFactorSet || m_platformPluginDpiScalingActive ;
544
0
    for (QScreen *screen : screens)
545
0
        screen->d_func()->updateGeometry();
546
0
}
547
548
static const char scaleFactorProperty[] = "_q_scaleFactor";
549
550
/*
551
    Sets a per-screen scale factor.
552
*/
553
void QHighDpiScaling::setScreenFactor(QScreen *screen, qreal factor)
554
0
{
555
0
    qCDebug(lcHighDpi) << "Setting screen scale factor for" << screen << "to" << factor;
556
557
0
    if (!qFuzzyCompare(factor, qreal(1))) {
558
0
        m_screenFactorSet = true;
559
0
        m_active = true;
560
0
    }
561
562
0
    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
0
    const QString name = screen->name();
567
0
    if (name.isEmpty())
568
0
        screen->setProperty(scaleFactorProperty, QVariant(factor));
569
0
    else
570
0
        QHighDpiScaling::m_namedScreenScaleFactors.insert(name, factor);
571
572
0
    screen->d_func()->updateGeometry();
573
0
}
574
575
QPoint QHighDpiScaling::mapPositionToNative(const QPoint &pos, const QPlatformScreen *platformScreen)
576
0
{
577
0
    if (!platformScreen)
578
0
        return pos;
579
0
    const qreal scaleFactor = factor(platformScreen);
580
0
    const QPoint topLeft = platformScreen->geometry().topLeft();
581
0
    return (pos - topLeft) * scaleFactor + topLeft;
582
0
}
583
584
QPoint QHighDpiScaling::mapPositionFromNative(const QPoint &pos, const QPlatformScreen *platformScreen)
585
0
{
586
0
    if (!platformScreen)
587
0
        return pos;
588
0
    const qreal scaleFactor = factor(platformScreen);
589
0
    const QPoint topLeft = platformScreen->geometry().topLeft();
590
0
    return (pos - topLeft) / scaleFactor + topLeft;
591
0
}
592
593
qreal QHighDpiScaling::screenSubfactor(const QPlatformScreen *screen)
594
289k
{
595
289k
    auto factor = qreal(1.0);
596
289k
    if (!screen)
597
0
        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
289k
    bool screenPropertyUsed = false;
604
289k
    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
0
        if (auto qScreen = screen->screen()) {
609
0
            auto screenFactor = qScreen->property(scaleFactorProperty).toReal(&screenPropertyUsed);
610
0
            if (screenPropertyUsed)
611
0
                factor = screenFactor;
612
0
        }
613
614
0
        if (!screenPropertyUsed) {
615
0
            auto byNameIt = QHighDpiScaling::m_namedScreenScaleFactors.constFind(screen->name());
616
0
            if ((screenPropertyUsed = byNameIt != QHighDpiScaling::m_namedScreenScaleFactors.cend()))
617
0
                factor = *byNameIt;
618
0
        }
619
0
    }
620
621
289k
    if (!screenPropertyUsed && m_usePlatformPluginDpi)
622
289k
        factor = roundScaleFactor(rawScaleFactor(screen));
623
624
289k
    return factor;
625
289k
}
626
627
QDpi QHighDpiScaling::logicalDpi(const QScreen *screen)
628
0
{
629
    // (Note: m_active test is performed at call site.)
630
0
    if (!screen || !screen->handle())
631
0
        return QDpi(96, 96);
632
633
0
    if (!m_usePlatformPluginDpi) {
634
0
        const qreal screenScaleFactor = screenSubfactor(screen->handle());
635
0
        const QDpi dpi = QPlatformScreen::overrideDpi(screen->handle()->logicalDpi());
636
0
        return QDpi{ dpi.first / screenScaleFactor, dpi.second / screenScaleFactor };
637
0
    }
638
639
0
    const qreal scaleFactor = rawScaleFactor(screen->handle());
640
0
    const qreal roundedScaleFactor = roundScaleFactor(scaleFactor);
641
0
    return effectiveLogicalDpi(screen->handle(), scaleFactor, roundedScaleFactor);
642
0
}
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.
647
QScreen *QHighDpiScaling::screenForPosition(QHighDpiScaling::Point position, QScreen *guess)
648
0
{
649
0
    if (position.kind == QHighDpiScaling::Point::Invalid)
650
0
        return nullptr;
651
652
0
    auto getPlatformScreenGuess = [](QScreen *maybeScreen) -> QPlatformScreen * {
653
0
        if (maybeScreen)
654
0
            return maybeScreen->handle();
655
0
        if (QScreen *primary = QGuiApplication::primaryScreen())
656
0
            return primary->handle();
657
0
        return nullptr;
658
0
    };
659
660
0
    QPlatformScreen *platformGuess = getPlatformScreenGuess(guess);
661
0
    if (!platformGuess)
662
0
        return nullptr;
663
664
0
    auto onScreen = [](QHighDpiScaling::Point position, const QPlatformScreen *platformScreen) -> bool {
665
0
        return position.kind == Point::Native
666
0
          ?  platformScreen->geometry().contains(position.point)
667
0
          :  platformScreen->screen()->geometry().contains(position.point);
668
0
    };
669
670
    // is the guessed screen correct?
671
0
    if (onScreen(position, platformGuess))
672
0
        return platformGuess->screen();
673
674
    // search sibling screens
675
0
    const auto screens = platformGuess->virtualSiblings();
676
0
    for (const QPlatformScreen *screen : screens) {
677
0
        if (onScreen(position, screen))
678
0
            return screen->screen();
679
0
    }
680
681
0
    return nullptr;
682
0
}
683
684
QList<QHighDpiScaling::ScreenFactor> QHighDpiScaling::parseScreenScaleFactorsSpec(QStringView screenScaleFactors)
685
144k
{
686
144k
    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
144k
    const auto specs = screenScaleFactors.split(u';');
692
144k
    for (const auto &spec : specs) {
693
144k
        const qsizetype equalsPos = spec.lastIndexOf(u'=');
694
144k
        if (equalsPos == -1) {
695
            // screens in order
696
144k
            bool ok;
697
144k
            const qreal factor = spec.toDouble(&ok);
698
144k
            if (ok && factor > 0) {
699
0
                screenFactors.append(QHighDpiScaling::ScreenFactor(QString(), factor));
700
0
            }
701
144k
        } else {
702
            // "name=factor"
703
0
            bool ok;
704
0
            const qreal factor = spec.mid(equalsPos + 1).toDouble(&ok);
705
0
            if (ok && factor > 0) {
706
0
                screenFactors.append(QHighDpiScaling::ScreenFactor(spec.left(equalsPos).toString(), factor));
707
0
            }
708
0
        }
709
144k
    } // for (specs)
710
711
144k
    return screenFactors;
712
144k
}
713
714
QHighDpiScaling::ScaleAndOrigin QHighDpiScaling::scaleAndOrigin(const QPlatformScreen *platformScreen, QHighDpiScaling::Point position)
715
289k
{
716
289k
    Q_UNUSED(position)
717
289k
    if (!m_active)
718
144k
        return { qreal(1), QPoint() };
719
144k
    if (!platformScreen)
720
0
        return { m_factor, QPoint() }; // the global factor
721
144k
    return { m_factor * screenSubfactor(platformScreen), platformScreen->geometry().topLeft() };
722
144k
}
723
724
QHighDpiScaling::ScaleAndOrigin QHighDpiScaling::scaleAndOrigin(const QScreen *screen, QHighDpiScaling::Point position)
725
0
{
726
0
    Q_UNUSED(position)
727
0
    if (!m_active)
728
0
        return { qreal(1), QPoint() };
729
0
    if (!screen)
730
0
        return { m_factor, QPoint() }; // the global factor
731
0
    return scaleAndOrigin(screen->handle(), position);
732
0
}
733
734
QHighDpiScaling::ScaleAndOrigin QHighDpiScaling::scaleAndOrigin(const QWindow *window, QHighDpiScaling::Point position)
735
0
{
736
0
    if (!m_active)
737
0
        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
0
    QScreen *screen = window ? window->screen() : QGuiApplication::primaryScreen();
742
0
    QScreen *overrideScreen = QHighDpiScaling::screenForPosition(position, screen);
743
0
    QScreen *targetScreen = overrideScreen ? overrideScreen : screen;
744
0
    return scaleAndOrigin(targetScreen, position);
745
0
}
746
747
#ifndef QT_NO_DEBUG_STREAM
748
QDebug operator<<(QDebug debug, const QHighDpiScaling::ScreenFactor &factor)
749
0
{
750
0
    const QDebugStateSaver saver(debug);
751
0
    debug.nospace();
752
0
    if (!factor.name.isEmpty())
753
0
        debug << factor.name << "=";
754
0
    debug << factor.factor;
755
0
    return debug;
756
0
}
757
#endif
758
759
#else // QT_NO_HIGHDPISCALING
760
761
QHighDpiScaling::ScaleAndOrigin QHighDpiScaling::scaleAndOrigin(const QPlatformScreen *, QPoint *)
762
{
763
    return { qreal(1), QPoint() };
764
}
765
766
QHighDpiScaling::ScaleAndOrigin QHighDpiScaling::scaleAndOrigin(const QScreen *, QPoint *)
767
{
768
    return { qreal(1), QPoint() };
769
}
770
771
QHighDpiScaling::ScaleAndOrigin QHighDpiScaling::scaleAndOrigin(const QWindow *, QPoint *)
772
{
773
    return { qreal(1), QPoint() };
774
}
775
776
#endif // QT_NO_HIGHDPISCALING
777
778
QT_END_NAMESPACE
779
780
#include "moc_qhighdpiscaling_p.cpp"