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