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 // Remove removed screens
246 for (const QString &remove : std::as_const(removed)) {
247 QOffscreenScreen *screen = platformScreenByName(remove, m_screens);
248 m_screens.removeAll(screen);
249 QWindowSystemInterface::handleScreenRemoved(screen);
250 }
251
252 // Add new screens
253 for (const QString &add : std::as_const(added)) {
254 QJsonValue configValue = screenConfigByName(add, newScreens);
255 QJsonObject config = configValue.toObject();
256 if (config.isEmpty()) {
257 qWarning("empty screen object");
258 continue;
259 }
260 QOffscreenScreen *offscreenScreen = new QOffscreenScreen(this);
261 offscreenScreen->m_name = config["name"].toString();
262 offscreenScreen->m_geometry = geometryFromConfig(config);
263 offscreenScreen->m_logicalDpi = config["logicalDpi"].toInt(96);
264 offscreenScreen->m_logicalBaseDpi = config["logicalBaseDpi"].toInt(96);
265 offscreenScreen->m_dpr = config["dpr"].toDouble(1.0);
266 m_screens.append(offscreenScreen);
267 QWindowSystemInterface::handleScreenAdded(offscreenScreen);
268 }
269
270 // Update present screens
271 for (const QString &pres : std::as_const(present)) {
272 QOffscreenScreen *screen = platformScreenByName(pres, m_screens);
273 Q_ASSERT(screen);
274 QJsonObject currentConfig = screenConfigByName(pres, currentScreens).toObject();
275 QJsonObject newConfig = screenConfigByName(pres, newScreens).toObject();
276
277 // Name can't change, because it'd be a different screen
278 Q_ASSERT(currentConfig["name"] == newConfig["name"]);
279
280 // Geometry
281 QRect currentGeomtry = geometryFromConfig(currentConfig);
282 QRect newGeomtry = geometryFromConfig(newConfig);
283 if (currentGeomtry != newGeomtry) {
284 screen->m_geometry = newGeomtry;
285 QWindowSystemInterface::handleScreenGeometryChange(screen->screen(), newGeomtry, newGeomtry);
286 }
287
288 // logical DPI
289 int currentLogicalDpi = currentConfig["logicalDpi"].toInt(96);
290 int newLogicalDpi = newConfig["logicalDpi"].toInt(96);
291 if (currentLogicalDpi != newLogicalDpi) {
292 screen->m_logicalDpi = newLogicalDpi;
293 QWindowSystemInterface::handleScreenLogicalDotsPerInchChange(screen->screen(), newLogicalDpi, newLogicalDpi);
294 }
295
296 // The base DPI is more of a platform constant, and should not change, and
297 // there is no handleChange function for it. Print a warning.
298 int currentLogicalBaseDpi = currentConfig["logicalBaseDpi"].toInt(96);
299 int newLogicalBaseDpi = newConfig["logicalBaseDpi"].toInt(96);
300 if (currentLogicalBaseDpi != newLogicalBaseDpi) {
301 screen->m_logicalBaseDpi = newLogicalBaseDpi;
302 qWarning("You ain't supposed to change logicalBaseDpi - its a platform constant. Qt may not react to the change");
303 }
304
305 // DPR. There is also no handleChange function in Qt at this point, instead
306 // the new DPR value will be used during the next repaint. We could repaint
307 // all windows here, but don't. Print a warning.
308 double currentDpr = currentConfig["dpr"].toDouble(1);
309 double newDpr = newConfig["dpr"].toDouble(1);
310 if (currentDpr != newDpr) {
311 screen->m_dpr = newDpr;
312 qWarning("DPR change notifications is not implemented - Qt may not react to the change");
313 }
314 }
315
316 // Now the new configuration is the current configuration
317 m_configuration = configuration;
318}
319
321{
322 return m_configuration;
323}
324
326{
327 m_inputContext.reset(QPlatformInputContextFactory::create());
328}
329
331{
332 return m_inputContext.data();
333}
334
335bool QOffscreenIntegration::hasCapability(QPlatformIntegration::Capability cap) const
336{
337 switch (cap) {
338 case ThreadedPixmaps: return true;
339 case MultipleWindows: return true;
340 case RhiBasedRendering: return false;
341 default: return QPlatformIntegration::hasCapability(cap);
342 }
343}
344
346{
347 Q_UNUSED(window);
348 QPlatformWindow *w = new QOffscreenWindow(window, m_windowFrameMarginsEnabled);
349 w->requestActivateWindow();
350 return w;
351}
352
354{
355 return new QOffscreenBackingStore(window);
356}
357
359{
360#if defined(Q_OS_UNIX)
361 return createUnixEventDispatcher();
362#elif defined(Q_OS_WIN)
363 return new QOffscreenEventDispatcher<QEventDispatcherWin32>();
364#else
365 return 0;
366#endif
367}
368
370{
371 if (!m_nativeInterface)
372 m_nativeInterface.reset(new QOffscreenPlatformNativeInterface(const_cast<QOffscreenIntegration*>(this)));
373 return m_nativeInterface.get();
374}
375
376static QString themeName() { return QStringLiteral("offscreen"); }
377
379{
380 return QStringList(themeName());
381}
382
383// Restrict the styles to "fusion" to prevent native styles requiring native
384// window handles (eg Windows Vista style) from being used.
386{
387public:
389
390 QVariant themeHint(ThemeHint h) const override
391 {
392 switch (h) {
393 case StyleNames:
394 return QVariant(QStringList(QStringLiteral("Fusion")));
395 default:
396 break;
397 }
398 return QPlatformTheme::themeHint(h);
399 }
400
401 virtual const QFont *font(Font type = SystemFont) const override
402 {
403 static QFont systemFont("Sans Serif"_L1, 9);
404 static QFont fixedFont("monospace"_L1, 9);
405 switch (type) {
406 case QPlatformTheme::SystemFont:
407 return &systemFont;
408 case QPlatformTheme::FixedFont:
409 return &fixedFont;
410 default:
411 return nullptr;
412 }
413 }
414};
415
417{
418 return name == themeName() ? new OffscreenTheme() : nullptr;
419}
420
422{
423 return m_fontDatabase.data();
424}
425
426#if QT_CONFIG(draganddrop)
427QPlatformDrag *QOffscreenIntegration::drag() const
428{
429 return m_drag.data();
430}
431#endif
432
434{
435 if (m_services.isNull())
436 m_services.reset(new QPlatformServices);
437
438 return m_services.data();
439}
440
441QOffscreenIntegration *QOffscreenIntegration::createOffscreenIntegration(const QStringList& paramList)
442{
443 QOffscreenIntegration *offscreenIntegration = nullptr;
444
445#if QT_CONFIG(xlib) && QT_CONFIG(opengl) && !QT_CONFIG(opengles2)
446 QByteArray glx = qgetenv("QT_QPA_OFFSCREEN_NO_GLX");
447 if (glx.isEmpty())
448 offscreenIntegration = new QOffscreenX11Integration(paramList);
449#endif
450
451 if (!offscreenIntegration)
452 offscreenIntegration = new QOffscreenIntegration(paramList);
453 return offscreenIntegration;
454}
455
457{
458 return m_screens;
459}
460
461QT_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()