Coverage Report

Created: 2026-02-10 07:39

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