Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qoffscreenintegration.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
8
9#if defined(Q_OS_UNIX)
10#include <QtGui/private/qgenericunixeventdispatcher_p.h>
11#if defined(Q_OS_APPLE)
12#include <qpa/qplatformfontdatabase.h>
13#include <QtGui/private/qcoretextfontdatabase_p.h>
14#else
15#include <QtGui/private/qgenericunixfontdatabase_p.h>
16#endif
17#elif defined(Q_OS_WIN)
18#include <QtGui/private/qfreetypefontdatabase_p.h>
19#include <QtCore/private/qeventdispatcher_win_p.h>
20#endif
21
22#include <QtCore/qfile.h>
23#include <QtCore/qjsonarray.h>
24#include <QtCore/qjsondocument.h>
25#include <QtCore/qjsonobject.h>
26#include <QtCore/qjsonvalue.h>
27#include <QtGui/private/qpixmap_raster_p.h>
28#include <QtGui/private/qguiapplication_p.h>
29#if QT_CONFIG(draganddrop)
30#include <QtGui/private/qsimpledrag_p.h>
31#endif
32#include <qpa/qplatforminputcontextfactory_p.h>
33#include <qpa/qplatforminputcontext.h>
34#include <qpa/qplatformtheme.h>
35#include <qpa/qwindowsysteminterface.h>
36
37#include <qpa/qplatformservices.h>
38
39#if QT_CONFIG(xlib) && QT_CONFIG(opengl) && !QT_CONFIG(opengles2)
40#include "qoffscreenintegration_x11.h"
41#endif
42
44
45using namespace Qt::StringLiterals;
46
47class QCoreTextFontEngine;
48
49template <typename BaseEventDispatcher>
50class QOffscreenEventDispatcher : public BaseEventDispatcher
51{
52public:
53 explicit QOffscreenEventDispatcher(QObject *parent = nullptr)
54 : BaseEventDispatcher(parent)
55 {
56 }
57
58 bool processEvents(QEventLoop::ProcessEventsFlags flags) override
59 {
60 bool didSendEvents = BaseEventDispatcher::processEvents(flags);
61
62 return QWindowSystemInterface::sendWindowSystemEvents(flags) || didSendEvents;
63 }
64};
65
66QOffscreenIntegration::QOffscreenIntegration(const QStringList& paramList)
67{
68#if defined(Q_OS_UNIX)
69#if defined(Q_OS_APPLE)
70 m_fontDatabase.reset(new QCoreTextFontDatabaseEngineFactory<QCoreTextFontEngine>);
71#else
72 m_fontDatabase.reset(new QGenericUnixFontDatabase());
73#endif
74#elif defined(Q_OS_WIN)
75 m_fontDatabase.reset(new QFreeTypeFontDatabase());
76#endif
77
78#if QT_CONFIG(draganddrop)
79 // Use the in-process QSimpleDrag, so that QDrag::exec() behaves as it does on a
80 // real platform: it enters a nested event loop and delivers drag events through
81 // QWindowSystemInterface. That makes drag and drop testable headlessly, because
82 // QOffscreenCursor implements QCursor::setPos() -- which QSimpleDrag::startDrag()
83 // needs to find the source window -- and synthetic mouse events can then drive and
84 // terminate the loop. QT_QPA_OFFSCREEN_NO_DND restores the old stub, which returns
85 // Qt::IgnoreAction immediately, for anything that cannot terminate the loop.
86 if (qEnvironmentVariableIsSet("QT_QPA_OFFSCREEN_NO_DND")
87 || paramList.contains("nodnd"_L1)) {
88 m_drag.reset(new QOffscreenDrag);
89 } else {
90 m_drag.reset(new QSimpleDrag);
91 }
92#endif
93
94 QJsonObject config = resolveConfigFileConfiguration(paramList).value_or(defaultConfiguration());
95 setConfiguration(config);
96}
97
99{
100 while (!m_screens.isEmpty())
101 QWindowSystemInterface::handleScreenRemoved(m_screens.takeLast());
102}
103
104/*
105 The offscren platform plugin is configurable with a JSON configuration.
106 The confiuration can be provided either from a file on disk on startup,
107 or at by calling setConfiguration().
108
109 To provide a configuration on startuip, write the config to disk and pass
110 the file path as a platform argument:
111
112 ./myapp -platform offscreen:configfile=/path/to/config.json
113
114 The supported top-level config keys are:
115 {
116 "synchronousWindowSystemEvents": <bool>
117 "windowFrameMargins": <bool>,
118 "screens": [<screens>],
119 }
120
121 "screens" is an array of:
122 {
123 "name": string,
124 "x": int,
125 "y": int,
126 "width": int,
127 "height": int,
128 "logicalDpi": int,
129 "logicalBaseDpi": int,
130 "dpr": double,
131 }
132*/
133
135{
136 const auto defaultScreen = QJsonObject {
137 {"name", ""},
138 {"x", 0},
139 {"y", 0},
140 {"width", 800},
141 {"height", 800},
142 {"logicalDpi", 96},
143 {"logicalBaseDpi", 96},
144 {"dpr", 1.0},
145 };
146 const auto defaultConfiguration = QJsonObject {
147 {"synchronousWindowSystemEvents", false},
148 {"windowFrameMargins", true},
149 {"screens", QJsonArray { defaultScreen } },
150 };
151 return defaultConfiguration;
152}
153
155{
156 bool hasConfigFile = false;
157 QString configFilePath;
158 for (const QString &param : paramList) {
159 // Look for "configfile=/path/to/file/"
160 QString configPrefix("configfile="_L1);
161 if (param.startsWith(configPrefix)) {
162 hasConfigFile = true;
163 configFilePath = param.mid(configPrefix.size());
164 }
165 }
166 if (!hasConfigFile)
167 return std::nullopt;
168
169 // Read config file
170 if (configFilePath.isEmpty())
171 qFatal("Missing file path for -configfile platform option");
172 QFile configFile(configFilePath);
173 if (!configFile.exists())
174 qFatal("Could not find platform config file %s", qPrintable(configFilePath));
175 if (!configFile.open(QIODevice::ReadOnly))
176 qFatal("Could not open platform config file for reading %s, %s", qPrintable(configFilePath), qPrintable(configFile.errorString()));
177
178 QByteArray json = configFile.readAll();
179 QJsonParseError error;
180 QJsonDocument config = QJsonDocument::fromJson(json, &error);
181 if (config.isNull())
182 qFatal("Platform config file parse error: %s", qPrintable(error.errorString()));
183
184 return config.object();
185}
186
187
188void QOffscreenIntegration::setConfiguration(const QJsonObject &configuration)
189{
190 // Apply the new configuration, diffing against the current m_configuration
191
192 const bool synchronousWindowSystemEvents = configuration["synchronousWindowSystemEvents"].toBool(
193 m_configuration["synchronousWindowSystemEvents"].toBool(false));
194 QWindowSystemInterface::setSynchronousWindowSystemEvents(synchronousWindowSystemEvents);
195
196 m_windowFrameMarginsEnabled = configuration["windowFrameMargins"].toBool(
197 m_configuration["windowFrameMargins"].toBool(true));
198
199 // Diff screens array, using the screen name as the screen identity.
200 QJsonArray currentScreens = m_configuration["screens"].toArray();
201 QJsonArray newScreens = configuration["screens"].toArray();
202
203 auto getScreenNames = [](const QJsonArray &screens) -> QList<QString> {
204 QList<QString> names;
205 for (const QJsonValue &screen : screens) {
206 names.append(screen["name"].toString());
207 };
208 std::sort(names.begin(), names.end());
209 return names;
210 };
211
212 auto currentNames = getScreenNames(currentScreens);
213 auto newNames = getScreenNames(newScreens);
214
215 QList<QString> present;
216 std::set_intersection(currentNames.begin(), currentNames.end(), newNames.begin(), newNames.end(),
217 std::inserter(present, present.begin()));
218 QList<QString> added;
219 std::set_difference(newNames.begin(), newNames.end(), currentNames.begin(), currentNames.end(),
220 std::inserter(added, added.begin()));
221 QList<QString> removed;
222 std::set_difference(currentNames.begin(), currentNames.end(), newNames.begin(), newNames.end(),
223 std::inserter(removed, removed.begin()));
224
225 auto platformScreenByName = [](const QString &name, const QList<QOffscreenScreen *> &screens) -> QOffscreenScreen * {
226 for (QOffscreenScreen *screen : screens) {
227 if (screen->m_name == name)
228 return screen;
229 }
230 Q_UNREACHABLE();
231 };
232
233 auto screenConfigByName = [](const QString &name, const QJsonArray &screenConfigs) -> QJsonValue {
234 for (const QJsonValue &screenConfig : screenConfigs) {
235 if (screenConfig["name"].toString() == name)
236 return screenConfig;
237 }
238 Q_UNREACHABLE();
239 };
240
241 auto geometryFromConfig = [](const QJsonObject &config) -> QRect {
242 return QRect(config["x"].toInt(0), config["y"].toInt(0), config["width"].toInt(640), config["height"].toInt(480));
243 };
244
245 auto availableGeometryFromConfig = [](const QJsonObject &config, QRect geometry) -> QRect {
246 const QJsonValue value = config["availableGeometry"];
247 if (!value.isObject())
248 return geometry;
249 const QJsonObject a = value.toObject();
250 return QRect(a["x"].toInt(geometry.x()), a["y"].toInt(geometry.y()),
251 a["width"].toInt(geometry.width()), a["height"].toInt(geometry.height()));
252 };
253
254 // Remove removed screens
255 for (const QString &remove : std::as_const(removed)) {
256 QOffscreenScreen *screen = platformScreenByName(remove, m_screens);
257 m_screens.removeAll(screen);
258 QWindowSystemInterface::handleScreenRemoved(screen);
259 }
260
261 // Add new screens
262 for (const QString &add : std::as_const(added)) {
263 QJsonValue configValue = screenConfigByName(add, newScreens);
264 QJsonObject config = configValue.toObject();
265 if (config.isEmpty()) {
266 qWarning("empty screen object");
267 continue;
268 }
269 QOffscreenScreen *offscreenScreen = new QOffscreenScreen(this);
270 offscreenScreen->m_name = config["name"].toString();
271 offscreenScreen->m_geometry = geometryFromConfig(config);
272 offscreenScreen->m_availableGeometry = availableGeometryFromConfig(config, offscreenScreen->m_geometry);
273 offscreenScreen->m_logicalDpi = config["logicalDpi"].toInt(96);
274 offscreenScreen->m_logicalBaseDpi = config["logicalBaseDpi"].toInt(96);
275 offscreenScreen->m_dpr = config["dpr"].toDouble(1.0);
276 m_screens.append(offscreenScreen);
277 QWindowSystemInterface::handleScreenAdded(offscreenScreen);
278 }
279
280 // Update present screens
281 for (const QString &pres : std::as_const(present)) {
282 QOffscreenScreen *screen = platformScreenByName(pres, m_screens);
283 Q_ASSERT(screen);
284 QJsonObject currentConfig = screenConfigByName(pres, currentScreens).toObject();
285 QJsonObject newConfig = screenConfigByName(pres, newScreens).toObject();
286
287 // Name can't change, because it'd be a different screen
288 Q_ASSERT(currentConfig["name"] == newConfig["name"]);
289
290 // Geometry
291 QRect currentGeomtry = geometryFromConfig(currentConfig);
292 QRect newGeomtry = geometryFromConfig(newConfig);
293 if (currentGeomtry != newGeomtry) {
294 screen->m_geometry = newGeomtry;
295 QWindowSystemInterface::handleScreenGeometryChange(screen->screen(), newGeomtry, newGeomtry);
296 }
297
298 // logical DPI
299 int currentLogicalDpi = currentConfig["logicalDpi"].toInt(96);
300 int newLogicalDpi = newConfig["logicalDpi"].toInt(96);
301 if (currentLogicalDpi != newLogicalDpi) {
302 screen->m_logicalDpi = newLogicalDpi;
303 QWindowSystemInterface::handleScreenLogicalDotsPerInchChange(screen->screen(), newLogicalDpi, newLogicalDpi);
304 }
305
306 // The base DPI is more of a platform constant, and should not change, and
307 // there is no handleChange function for it. Print a warning.
308 int currentLogicalBaseDpi = currentConfig["logicalBaseDpi"].toInt(96);
309 int newLogicalBaseDpi = newConfig["logicalBaseDpi"].toInt(96);
310 if (currentLogicalBaseDpi != newLogicalBaseDpi) {
311 screen->m_logicalBaseDpi = newLogicalBaseDpi;
312 qWarning("You ain't supposed to change logicalBaseDpi - its a platform constant. Qt may not react to the change");
313 }
314
315 // DPR. There is also no handleChange function in Qt at this point, instead
316 // the new DPR value will be used during the next repaint. We could repaint
317 // all windows here, but don't. Print a warning.
318 double currentDpr = currentConfig["dpr"].toDouble(1);
319 double newDpr = newConfig["dpr"].toDouble(1);
320 if (currentDpr != newDpr) {
321 screen->m_dpr = newDpr;
322 qWarning("DPR change notifications is not implemented - Qt may not react to the change");
323 }
324 }
325
326 // Now the new configuration is the current configuration
327 m_configuration = configuration;
328}
329
331{
332 return m_configuration;
333}
334
336{
337 m_inputContext.reset(QPlatformInputContextFactory::create());
338}
339
341{
342 return m_inputContext.data();
343}
344
345bool QOffscreenIntegration::hasCapability(QPlatformIntegration::Capability cap) const
346{
347 switch (cap) {
348 case ThreadedPixmaps: return true;
349 case MultipleWindows: return true;
350 case RhiBasedRendering: return false;
351 default: return QPlatformIntegration::hasCapability(cap);
352 }
353}
354
356{
357 Q_UNUSED(window);
358 QPlatformWindow *w = new QOffscreenWindow(window, m_windowFrameMarginsEnabled);
359 w->requestActivateWindow();
360 return w;
361}
362
364{
365 return new QOffscreenBackingStore(window);
366}
367
369{
370#if defined(Q_OS_UNIX)
371 return createUnixEventDispatcher();
372#elif defined(Q_OS_WIN)
373 return new QOffscreenEventDispatcher<QEventDispatcherWin32>();
374#else
375 return 0;
376#endif
377}
378
380{
381 if (!m_nativeInterface)
382 m_nativeInterface.reset(new QOffscreenPlatformNativeInterface(const_cast<QOffscreenIntegration*>(this)));
383 return m_nativeInterface.get();
384}
385
386static QString themeName() { return QStringLiteral("offscreen"); }
387
389{
390 return QStringList(themeName());
391}
392
393// Restrict the styles to "fusion" to prevent native styles requiring native
394// window handles (eg Windows Vista style) from being used.
396{
397public:
399
400 QVariant themeHint(ThemeHint h) const override
401 {
402 switch (h) {
403 case StyleNames:
404 return QVariant(QStringList(QStringLiteral("Fusion")));
405 default:
406 break;
407 }
408 return QPlatformTheme::themeHint(h);
409 }
410
411 virtual const QFont *font(Font type = SystemFont) const override
412 {
413 static QFont systemFont("Sans Serif"_L1, 9);
414 static QFont fixedFont("monospace"_L1, 9);
415 switch (type) {
416 case QPlatformTheme::SystemFont:
417 return &systemFont;
418 case QPlatformTheme::FixedFont:
419 return &fixedFont;
420 default:
421 return nullptr;
422 }
423 }
424};
425
427{
428 return name == themeName() ? new OffscreenTheme() : nullptr;
429}
430
432{
433 return m_fontDatabase.data();
434}
435
436#if QT_CONFIG(draganddrop)
437QPlatformDrag *QOffscreenIntegration::drag() const
438{
439 return m_drag.data();
440}
441#endif
442
444{
445 if (m_services.isNull())
446 m_services.reset(new QPlatformServices);
447
448 return m_services.data();
449}
450
451QOffscreenIntegration *QOffscreenIntegration::createOffscreenIntegration(const QStringList& paramList)
452{
453 QOffscreenIntegration *offscreenIntegration = nullptr;
454
455#if QT_CONFIG(xlib) && QT_CONFIG(opengl) && !QT_CONFIG(opengles2)
456 QByteArray glx = qgetenv("QT_QPA_OFFSCREEN_NO_GLX");
457 if (glx.isEmpty())
458 offscreenIntegration = new QOffscreenX11Integration(paramList);
459#endif
460
461 if (!offscreenIntegration)
462 offscreenIntegration = new QOffscreenIntegration(paramList);
463 return offscreenIntegration;
464}
465
467{
468 return m_screens;
469}
470
471QT_END_NAMESPACE
QVariant themeHint(ThemeHint h) const override
virtual const QFont * font(Font type=SystemFont) const override
\inmodule QtCore\reentrant
Definition qjsonobject.h:34
QOffscreenEventDispatcher(QObject *parent=nullptr)
bool processEvents(QEventLoop::ProcessEventsFlags flags) override
QPlatformFontDatabase * fontDatabase() const override
Accessor for the platform integration's fontdatabase.
std::optional< QJsonObject > resolveConfigFileConfiguration(const QStringList &paramList) const
void initialize() override
Performs initialization steps that depend on having an event dispatcher available.
QList< QOffscreenScreen * > screens() const
QPlatformWindow * createPlatformWindow(QWindow *window) const override
Factory function for QPlatformWindow.
QJsonObject configuration() const
QPlatformBackingStore * createPlatformBackingStore(QWindow *window) const override
Factory function for QPlatformBackingStore.
QPlatformTheme * createPlatformTheme(const QString &name) const override
QPlatformInputContext * inputContext() const override
Returns the platforms input context.
QOffscreenIntegration(const QStringList &paramList)
QPlatformServices * services() const override
QPlatformNativeInterface * nativeInterface() const override
bool hasCapability(QPlatformIntegration::Capability cap) const override
QJsonObject defaultConfiguration() const
QStringList themeNames() const override
void setConfiguration(const QJsonObject &configuration)
QAbstractEventDispatcher * createEventDispatcher() const override
Factory function for the GUI event dispatcher.
Combined button and popup list for selecting options.
static QString themeName()